diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua.hs index 6dd7312e05e0..522b0dc08663 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua.hs @@ -17,6 +17,7 @@ module Text.Pandoc.Lua , setGlobals , runLua , runLuaNoEnv + , userInit -- * Engine , getEngine ) where @@ -24,5 +25,6 @@ module Text.Pandoc.Lua import Text.Pandoc.Lua.Custom (loadCustom) import Text.Pandoc.Lua.Engine (getEngine, applyFilter) import Text.Pandoc.Lua.Global (Global (..), setGlobals) +import Text.Pandoc.Lua.Init (userInit) import Text.Pandoc.Lua.Run (runLua, runLuaNoEnv) import Text.Pandoc.Lua.Orphans () diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Custom.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Custom.hs index ff78f5f5d0d9..f09f29c7796e 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Custom.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Custom.hs @@ -18,6 +18,7 @@ import HsLua as Lua hiding (Operation (Div)) import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Global (Global (..), setGlobals) +import Text.Pandoc.Lua.Init (userInit) import Text.Pandoc.Lua.Marshal.Format (peekExtensionsConfig) import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc) import Text.Pandoc.Lua.Marshal.WriterOptions (pushWriterOptions) @@ -34,10 +35,12 @@ import qualified Text.Pandoc.Class as PandocMonad loadCustom :: (PandocMonad m, MonadIO m) => FilePath -> m (CustomComponents m) loadCustom luaFile = do + initialState <- PandocMonad.getCommonState luaState <- liftIO newGCManagedState luaFile' <- fromMaybe luaFile <$> findFileWithDataFallback "custom" luaFile either throw pure <=< runLuaWith luaState $ do + userInit initialState let globals = [ PANDOC_SCRIPT_FILE luaFile' ] setGlobals globals dofileTrace (Just luaFile') >>= \case diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs index f09189c03dd2..6dbbccf80901 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs @@ -17,15 +17,16 @@ import Control.Exception (throw) import Control.Monad ((>=>)) import Control.Monad.IO.Class (MonadIO (liftIO)) import HsLua.Core (getglobal, openlibs, run, top, tostring) -import Text.Pandoc.Class (PandocMonad) +import Text.Pandoc.Class (PandocMonad (getCommonState)) import Text.Pandoc.Definition (Pandoc) import Text.Pandoc.Filter (Environment (..)) import Text.Pandoc.Error (PandocError (PandocFilterError, PandocLuaError)) import Text.Pandoc.Lua.Custom (loadCustom) import Text.Pandoc.Lua.Filter (runFilterFile) import Text.Pandoc.Lua.Global (Global (..), setGlobals) -import Text.Pandoc.Lua.Run (runLua) +import Text.Pandoc.Lua.Init (userInit) import Text.Pandoc.Lua.Orphans () +import Text.Pandoc.Lua.Run (runLua) import Text.Pandoc.Scripting (ScriptingEngine (..)) import qualified Text.Pandoc.UTF8 as UTF8 import qualified Data.Text as T @@ -60,7 +61,9 @@ applyFilter fenv args fp doc = do , PANDOC_WRITER_OPTIONS (envWriterOptions fenv) , PANDOC_SCRIPT_FILE fp ] + st <- getCommonState runLua >=> forceResult fp $ do + userInit st setGlobals globals runFilterFile fp doc diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Init.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Init.hs index f16e0be441df..32aff552af70 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Init.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Init.hs @@ -17,7 +17,7 @@ module Text.Pandoc.Lua.Init import Control.Monad (when) import Control.Monad.Catch (throwM) import HsLua as Lua hiding (status) -import Text.Pandoc.Class (report) +import Text.Pandoc.Class (PandocMonad (putCommonState), CommonState, report) import Text.Pandoc.Data (readDataFile) import Text.Pandoc.Error (PandocError (PandocLuaError)) import Text.Pandoc.Logging (LogMessage (ScriptingWarning)) @@ -34,11 +34,12 @@ initLua = do liftPandocLua Lua.openlibs setWarnFunction initModules - liftPandocLua userInit -- | User-controlled initialization, e.g., running the user's init script. -userInit :: LuaE PandocError () -userInit = runInitScript +userInit :: CommonState -> LuaE PandocError () +userInit st = do + unPandocLua $ putCommonState st + runInitScript -- | Run the @init.lua@ data file as a Lua script. runInitScript :: LuaE PandocError () diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/Chunks.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/Chunks.hs index 808ebeae505d..987c3f3f81a7 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/Chunks.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/Chunks.hs @@ -2,7 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Marshal.Chunks - Copyright : © 2022 Albert Krewinkel + Copyright : © 2022-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/CommonState.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/CommonState.hs index 043ccdb20b37..01053d1c0510 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/CommonState.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Marshal/CommonState.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Marshal.CommonState @@ -13,8 +14,10 @@ module Text.Pandoc.Lua.Marshal.CommonState ( typeCommonState , peekCommonState , pushCommonState + , peekCommonStateFromTable ) where +import Data.Default (def) import HsLua import Text.Pandoc.Class (CommonState (..)) import Text.Pandoc.Lua.Marshal.List (pushPandocList) @@ -57,3 +60,31 @@ peekCommonState = peekUD typeCommonState pushCommonState :: LuaError e => Pusher e CommonState pushCommonState = pushUD typeCommonState + +peekCommonStateFromTable :: LuaError e => Peeker e CommonState +peekCommonStateFromTable idx = do + absidx <- liftLua $ absindex idx + let setnext st = do + liftLua (next absidx) >>= \case + False -> pure st + True -> do + prop <- peekName (nth 2) + case lookup prop setters of + Just setter -> setnext =<< setter top st `lastly` pop 1 + Nothing -> failPeek ("Unknown field " <> fromName prop) + `lastly` pop 1 + liftLua pushnil + setnext def + +setters :: LuaError e + => [ (Name, StackIndex -> CommonState -> Peek e CommonState)] +setters = + [ ("input_files", mkS (peekList peekString) (\st x -> st{stInputFiles = x})) + , ("output_file", mkS (peekNilOr peekString) (\st x -> st{stOutputFile = x})) + , ("request_headers", mkS (peekList (peekPair peekText peekText)) + (\st x -> st{ stRequestHeaders = x })) + , ("user_data_dir", mkS (peekNilOr peekString) (\st x -> st{stUserDataDir = x})) + , ("trace", mkS peekBool (\st x -> st{stTrace = x})) + ] + where + mkS peekX setValue idx' st = setValue st <$> peekX idx' diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/Module/Pandoc.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/Module/Pandoc.hs index 1a33f266a081..e440e7f5f3db 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/Module/Pandoc.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/Module/Pandoc.hs @@ -28,11 +28,13 @@ import Data.Proxy (Proxy (Proxy)) import Data.Text.Encoding.Error (UnicodeException) import HsLua import System.Exit (ExitCode (..)) +import Text.Pandoc.Class (PandocMonad(putCommonState)) import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError (..)) import Text.Pandoc.Format (parseFlavoredFormat) import Text.Pandoc.Lua.Orphans () import Text.Pandoc.Lua.Marshal.AST +import Text.Pandoc.Lua.Marshal.CommonState (peekCommonStateFromTable) import Text.Pandoc.Lua.Marshal.Format (peekFlavoredFormat) import Text.Pandoc.Lua.Marshal.Filter (peekFilter) import Text.Pandoc.Lua.Marshal.ReaderOptions ( peekReaderOptions @@ -194,7 +196,28 @@ stringConstants = functions :: [DocumentedFunction PandocError] functions = - [ defun "pipe" + [ defun "init" + ### (\newCommonState -> do + getfield registryindex "PANDOC_STATE" >>= \case + TypeNil -> True <$ unPandocLua (putCommonState newCommonState) + _ -> pure False) + <#> parameter peekCommonStateFromTable "table" "props" + "pandoc state properties" + =#> boolResult "Whether the initialization succeeded." + #? T.unlines + [ "Initialize the pandoc state. This function should be called at most" + , "once, as further invocations won't have any effect. The state is set" + , "only if it hasn't been initialized yet." + , "" + , "Note that the state is always already initialized in filters and in" + , "custom readers or writers. The function is most useful in standalone" + , "pandoc Lua programs." + , "" + , "Returns `true` if the initialization succeeded, and `false` if the Lua" + , "state had been initialized before." + ] + + , defun "pipe" ### (\command args input -> do (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input `catch` (throwM . PandocIOError "pipe") diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs index 9c4bebdd2247..99eb97ad7f33 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs @@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} @@ -11,7 +12,7 @@ License : GPL-2.0-or-later Maintainer : Albert Krewinkel -PandocMonad instance which allows execution of Lua operations and which +PandocMonad instance that allows execution of Lua operations; it uses Lua to handle state. -} module Text.Pandoc.Lua.PandocLua @@ -22,6 +23,7 @@ module Text.Pandoc.Lua.PandocLua import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) import Control.Monad.Except (MonadError (catchError, throwError)) import Control.Monad.IO.Class (MonadIO) +import Data.Default (def) import HsLua as Lua import Text.Pandoc.Class (PandocMonad (..)) import Text.Pandoc.Error (PandocError (..)) @@ -77,8 +79,13 @@ instance PandocMonad PandocLua where getModificationTime = IO.getModificationTime getCommonState = PandocLua $ do - Lua.getfield registryindex "PANDOC_STATE" - forcePeek $ peekCommonState Lua.top `lastly` pop 1 + -- initialize with the default value if is hadn't been initialized yet. + Lua.getfield registryindex "PANDOC_STATE" >>= \case + TypeNil -> do + pop 1 -- pop nil + unPandocLua $ putCommonState def + return def + _ -> forcePeek $ peekCommonState Lua.top `lastly` pop 1 putCommonState cst = PandocLua $ do pushCommonState cst Lua.pushvalue Lua.top diff --git a/pandoc-lua-engine/test/Tests/Lua.hs b/pandoc-lua-engine/test/Tests/Lua.hs index 76f3f467db44..7223651c4ff3 100644 --- a/pandoc-lua-engine/test/Tests/Lua.hs +++ b/pandoc-lua-engine/test/Tests/Lua.hs @@ -25,12 +25,13 @@ import Text.Pandoc.Builder (bulletList, definitionList, displayMath, divWith, singleQuoted, space, str, strong, HasMeta (setMeta)) import Text.Pandoc.Class ( CommonState (stVerbosity) + , PandocMonad (getCommonState) , modifyCommonState, runIOorExplode, setUserDataDir) import Text.Pandoc.Definition (Attr, Block (BlockQuote, Div, Para), Pandoc, Inline (Emph, Str), pandocTypesVersion) import Text.Pandoc.Error (PandocError (PandocLuaError)) import Text.Pandoc.Logging (Verbosity (ERROR)) -import Text.Pandoc.Lua (Global (..), applyFilter, runLua, setGlobals) +import Text.Pandoc.Lua (Global (..), applyFilter, runLua, setGlobals, userInit) import Text.Pandoc.Options (def) import Text.Pandoc.Version (pandocVersionText) @@ -243,7 +244,9 @@ runLuaTest op = runIOorExplode $ do -- Disable printing of warnings on stderr: some tests will generate -- warnings, we don't want to see those messages. modifyCommonState $ \st -> st { stVerbosity = ERROR } + st <- getCommonState res <- runLua $ do + userInit st setGlobals [ PANDOC_WRITER_OPTIONS def ] op case res of