git.iko.soy/typeable/pandoc
on master help sign in
Custom writer: use init file to setup Lua interpreter
4c64af4Albert Krewinkel
pandoc.cabal+2−1
@@ -523,9 +523,10 @@ library
523 Text.Pandoc.Readers.Org.ParserState,
523 Text.Pandoc.Readers.Org.ParserState,
524 Text.Pandoc.Readers.Org.Parsing,
524 Text.Pandoc.Readers.Org.Parsing,
525 Text.Pandoc.Readers.Org.Shared,
525 Text.Pandoc.Readers.Org.Shared,
526+ Text.Pandoc.Lua.Filter,
527+ Text.Pandoc.Lua.Init,
526 Text.Pandoc.Lua.Packages,
528 Text.Pandoc.Lua.Packages,
527 Text.Pandoc.Lua.PandocModule,
529 Text.Pandoc.Lua.PandocModule,
528− Text.Pandoc.Lua.Filter,
529 Text.Pandoc.Lua.StackInstances,
530 Text.Pandoc.Lua.StackInstances,
530 Text.Pandoc.Lua.Util,
531 Text.Pandoc.Lua.Util,
531 Text.Pandoc.CSS,
532 Text.Pandoc.CSS,
src/Text/Pandoc/App.hs+2−2
@@ -223,7 +223,7 @@ convertWithOpts opts = do
223 if ".lua" `isSuffixOf` format
223 if ".lua" `isSuffixOf` format
224 -- note: use non-lowercased version writerName
224 -- note: use non-lowercased version writerName
225 then return (TextWriter
225 then return (TextWriter
226− (\o d -> liftIO $ writeCustom writerName o d)
226+ (\o d -> writeCustom writerName o d)
227 :: Writer PandocIO, mempty)
227 :: Writer PandocIO, mempty)
228 else case getWriter writerName of
228 else case getWriter writerName of
229 Left e -> E.throwIO $ PandocAppError $
229 Left e -> E.throwIO $ PandocAppError $
@@ -846,7 +846,7 @@ applyLuaFilters :: Maybe FilePath -> [FilePath] -> String -> Pandoc
846applyLuaFilters mbDatadir filters format d = do
846applyLuaFilters mbDatadir filters format d = do
847 expandedFilters <- mapM (expandFilterPath mbDatadir) filters
847 expandedFilters <- mapM (expandFilterPath mbDatadir) filters
848 let go f d' = do
848 let go f d' = do
849− res <- runLuaFilter mbDatadir f format d'
849+ res <- runLuaFilter f format d'
850 case res of
850 case res of
851 Right x -> return x
851 Right x -> return x
852 Left (LuaException s) -> E.throw (PandocFilterError f s)
852 Left (LuaException s) -> E.throw (PandocFilterError f s)
src/Text/Pandoc/Lua.hs+13−42
@@ -27,43 +27,32 @@ Running pandoc Lua filters.
27-}
27-}
28module Text.Pandoc.Lua
28module Text.Pandoc.Lua
29 ( LuaException (..)
29 ( LuaException (..)
30− , LuaPackageParams (..)
31− , pushPandocModule
32 , runLuaFilter
30 , runLuaFilter
33− , initLuaState
31+ , runPandocLua
34− , luaPackageParams
32+ , pushPandocModule
35 ) where
33 ) where
36
34
37import Control.Monad (when, (>=>))
35import Control.Monad (when, (>=>))
38−import Control.Monad.Trans (MonadIO (..))
39−import Data.IORef (newIORef, readIORef)
40import Foreign.Lua (FromLuaStack (peek), Lua, LuaException (..),
36import Foreign.Lua (FromLuaStack (peek), Lua, LuaException (..),
41 Status (OK), ToLuaStack (push))
37 Status (OK), ToLuaStack (push))
42−import Text.Pandoc.Class (PandocIO, getCommonState, getMediaBag, setMediaBag)
38+import Text.Pandoc.Class (PandocIO)
43−import Text.Pandoc.Definition
39+import Text.Pandoc.Definition (Pandoc)
44import Text.Pandoc.Lua.Filter (LuaFilter, walkMWithLuaFilter)
40import Text.Pandoc.Lua.Filter (LuaFilter, walkMWithLuaFilter)
45−import Text.Pandoc.Lua.Packages (LuaPackageParams (..),
41+import Text.Pandoc.Lua.Init (runPandocLua)
46− installPandocPackageSearcher)
47import Text.Pandoc.Lua.PandocModule (pushPandocModule) -- TODO: remove
42import Text.Pandoc.Lua.PandocModule (pushPandocModule) -- TODO: remove
48−import Text.Pandoc.Lua.Util (loadScriptFromDataDir)
49import qualified Foreign.Lua as Lua
43import qualified Foreign.Lua as Lua
50−import qualified Foreign.Lua.Module.Text as Lua
51
44
45+-- | Run the Lua filter in @filterPath@ for a transformation to target
46+-- format @format@. Pandoc uses Lua init files to setup the Lua
47+-- interpreter.
52−runLuaFilter :: Maybe FilePath -> FilePath -> String
48+runLuaFilter :: FilePath -> String
53 -> Pandoc -> PandocIO (Either LuaException Pandoc)
49 -> Pandoc -> PandocIO (Either LuaException Pandoc)
54−runLuaFilter datadir filterPath format pd = do
50+runLuaFilter filterPath format doc =
55− luaPkgParams <- luaPackageParams datadir
56− res <- liftIO . Lua.runLuaEither $
57− runLuaFilter' luaPkgParams filterPath format pd
51+ runPandocLua (runLuaFilter' filterPath format doc)
58− newMediaBag <- liftIO (readIORef (luaPkgMediaBag luaPkgParams))
59− setMediaBag newMediaBag
60− return res
61
52
62−runLuaFilter' :: LuaPackageParams
63− -> FilePath -> String
53+runLuaFilter' :: FilePath -> String
64 -> Pandoc -> Lua Pandoc
54 -> Pandoc -> Lua Pandoc
65−runLuaFilter' luaPkgOpts filterPath format pd = do
55+runLuaFilter' filterPath format pd = do
66− initLuaState luaPkgOpts
67 -- store module in global "pandoc"
56 -- store module in global "pandoc"
68 registerFormat
57 registerFormat
69 top <- Lua.gettop
58 top <- Lua.gettop
@@ -83,24 +72,6 @@ runLuaFilter' luaPkgOpts filterPath format pd = do
83 push format
72 push format
84 Lua.setglobal "FORMAT"
73 Lua.setglobal "FORMAT"
85
74
86−luaPackageParams :: Maybe FilePath -> PandocIO LuaPackageParams
87−luaPackageParams datadir = do
88− commonState <- getCommonState
89− mbRef <- liftIO . newIORef =<< getMediaBag
90− return LuaPackageParams
91− { luaPkgCommonState = commonState
92− , luaPkgDataDir = datadir
93− , luaPkgMediaBag = mbRef
94− }
95−
96−-- Initialize the lua state with all required values
97−initLuaState :: LuaPackageParams -> Lua ()
98−initLuaState luaPkgParams = do
99− Lua.openlibs
100− Lua.preloadTextModule "text"
101− installPandocPackageSearcher luaPkgParams
102− loadScriptFromDataDir (luaPkgDataDir luaPkgParams) "init.lua"
103−
104pushGlobalFilter :: Lua ()
75pushGlobalFilter :: Lua ()
105pushGlobalFilter = do
76pushGlobalFilter = do
106 Lua.newtable
77 Lua.newtable
src/Text/Pandoc/Lua/Init.hsnew file+79
@@ -0,0 +1,79 @@
1+{-
2+Copyright © 2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
3+
4+This program is free software; you can redistribute it and/or modify
5+it under the terms of the GNU General Public License as published by
6+the Free Software Foundation; either version 2 of the License, or
7+(at your option) any later version.
8+
9+This program is distributed in the hope that it will be useful,
10+but WITHOUT ANY WARRANTY; without even the implied warranty of
11+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+GNU General Public License for more details.
13+
14+You should have received a copy of the GNU General Public License
15+along with this program; if not, write to the Free Software
16+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17+-}
18+{- |
19+ Module : Text.Pandoc.Lua
20+ Copyright : Copyright © 2017 Albert Krewinkel
21+ License : GNU GPL, version 2 or above
22+
23+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
24+ Stability : alpha
25+
26+Functions to initialize the Lua interpreter.
27+-}
28+module Text.Pandoc.Lua.Init
29+ ( LuaException (..)
30+ , LuaPackageParams (..)
31+ , runPandocLua
32+ , initLuaState
33+ , luaPackageParams
34+ ) where
35+
36+import Control.Monad.Trans (MonadIO (..))
37+import Data.IORef (newIORef, readIORef)
38+import Foreign.Lua (Lua, LuaException (..))
39+import GHC.IO.Encoding (getForeignEncoding, setForeignEncoding, utf8)
40+import Text.Pandoc.Class (PandocIO, getCommonState, getUserDataDir, getMediaBag,
41+ setMediaBag)
42+import Text.Pandoc.Lua.Packages (LuaPackageParams (..),
43+ installPandocPackageSearcher)
44+import Text.Pandoc.Lua.Util (loadScriptFromDataDir)
45+
46+import qualified Foreign.Lua as Lua
47+import qualified Foreign.Lua.Module.Text as Lua
48+
49+-- | Run the lua interpreter, using pandoc's default way of environment
50+-- initalization.
51+runPandocLua :: Lua a -> PandocIO (Either LuaException a)
52+runPandocLua luaOp = do
53+ datadir <- getUserDataDir
54+ luaPkgParams <- luaPackageParams datadir
55+ enc <- liftIO $ getForeignEncoding <* setForeignEncoding utf8
56+ res <- liftIO $ Lua.runLuaEither (initLuaState luaPkgParams *> luaOp)
57+ liftIO $ setForeignEncoding enc
58+ newMediaBag <- liftIO (readIORef (luaPkgMediaBag luaPkgParams))
59+ setMediaBag newMediaBag
60+ return res
61+
62+-- | Generate parameters required to setup pandoc's lua environment.
63+luaPackageParams :: Maybe FilePath -> PandocIO LuaPackageParams
64+luaPackageParams datadir = do
65+ commonState <- getCommonState
66+ mbRef <- liftIO . newIORef =<< getMediaBag
67+ return LuaPackageParams
68+ { luaPkgCommonState = commonState
69+ , luaPkgDataDir = datadir
70+ , luaPkgMediaBag = mbRef
71+ }
72+
73+-- Initialize the lua state with all required values
74+initLuaState :: LuaPackageParams -> Lua ()
75+initLuaState luaPkgParams = do
76+ Lua.openlibs
77+ Lua.preloadTextModule "text"
78+ installPandocPackageSearcher luaPkgParams
79+ loadScriptFromDataDir (luaPkgDataDir luaPkgParams) "init.lua"
src/Text/Pandoc/Writers/Custom.hs+12−11
@@ -33,18 +33,20 @@ module Text.Pandoc.Writers.Custom ( writeCustom ) where
33import Control.Arrow ((***))
33import Control.Arrow ((***))
34import Control.Exception
34import Control.Exception
35import Control.Monad (when)
35import Control.Monad (when)
36+import Control.Monad.Trans (MonadIO (liftIO))
36import Data.Char (toLower)
37import Data.Char (toLower)
37import Data.List (intersperse)
38import Data.List (intersperse)
38import qualified Data.Map as M
39import qualified Data.Map as M
39import Data.Text (Text, pack)
40import Data.Text (Text, pack)
40import Data.Typeable
41import Data.Typeable
41−import Foreign.Lua (Lua, ToLuaStack (..), callFunc, runLua)
42+import Foreign.Lua (Lua, ToLuaStack (..), callFunc)
42import Foreign.Lua.Api
43import Foreign.Lua.Api
43−import GHC.IO.Encoding (getForeignEncoding, setForeignEncoding, utf8)
44+import Text.Pandoc.Class (PandocIO)
44import Text.Pandoc.Definition
45import Text.Pandoc.Definition
45import Text.Pandoc.Error
46import Text.Pandoc.Error
47+import Text.Pandoc.Lua.Init (runPandocLua)
46import Text.Pandoc.Lua.StackInstances ()
48import Text.Pandoc.Lua.StackInstances ()
47−import Text.Pandoc.Lua.Util (addValue)
49+import Text.Pandoc.Lua.Util (addValue, dostring')
48import Text.Pandoc.Options
50import Text.Pandoc.Options
49import Text.Pandoc.Templates
51import Text.Pandoc.Templates
50import qualified Text.Pandoc.UTF8 as UTF8
52import qualified Text.Pandoc.UTF8 as UTF8
@@ -91,14 +93,11 @@ data PandocLuaException = PandocLuaException String
91instance Exception PandocLuaException
93instance Exception PandocLuaException
92
94
93-- | Convert Pandoc to custom markup.
95-- | Convert Pandoc to custom markup.
94−writeCustom :: FilePath -> WriterOptions -> Pandoc -> IO Text
96+writeCustom :: FilePath -> WriterOptions -> Pandoc -> PandocIO Text
95writeCustom luaFile opts doc@(Pandoc meta _) = do
97writeCustom luaFile opts doc@(Pandoc meta _) = do
96− luaScript <- UTF8.readFile luaFile
98+ luaScript <- liftIO $ UTF8.readFile luaFile
97− enc <- getForeignEncoding
99+ res <- runPandocLua $ do
98− setForeignEncoding utf8
99− (body, context) <- runLua $ do
100− openlibs
101− stat <- loadstring luaScript
100+ stat <- dostring' luaScript
102 -- check for error in lua script (later we'll change the return type
101 -- check for error in lua script (later we'll change the return type
103 -- to handle this more gracefully):
102 -- to handle this more gracefully):
104 when (stat /= OK) $
103 when (stat /= OK) $
@@ -111,7 +110,9 @@ writeCustom luaFile opts doc@(Pandoc meta _) = do
111 inlineListToCustom
110 inlineListToCustom
112 meta
111 meta
113 return (rendered, context)
112 return (rendered, context)
114− setForeignEncoding enc
113+ let (body, context) = case res of
114+ Left e -> throw (PandocLuaException (show e))
115+ Right x -> x
115 case writerTemplate opts of
116 case writerTemplate opts of
116 Nothing -> return $ pack body
117 Nothing -> return $ pack body
117 Just tpl ->
118 Just tpl ->
test/Tests/Lua.hs+18−13
@@ -10,9 +10,9 @@ import Text.Pandoc.Arbitrary ()
10import Text.Pandoc.Builder (bulletList, divWith, doc, doubleQuoted, emph,
10import Text.Pandoc.Builder (bulletList, divWith, doc, doubleQuoted, emph,
11 header, linebreak, para, plain, rawBlock,
11 header, linebreak, para, plain, rawBlock,
12 singleQuoted, space, str, strong, (<>))
12 singleQuoted, space, str, strong, (<>))
13−import Text.Pandoc.Class (runIOorExplode)
13+import Text.Pandoc.Class (runIOorExplode, setUserDataDir)
14import Text.Pandoc.Definition (Block, Inline, Meta, Pandoc)
14import Text.Pandoc.Definition (Block, Inline, Meta, Pandoc)
15−import Text.Pandoc.Lua (initLuaState, runLuaFilter, luaPackageParams)
15+import Text.Pandoc.Lua (runLuaFilter, runPandocLua)
16
16
17import qualified Foreign.Lua as Lua
17import qualified Foreign.Lua as Lua
18
18
@@ -95,8 +95,9 @@ tests = map (localOption (QuickCheckTests 20))
95
95
96assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
96assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
97assertFilterConversion msg filterPath docIn docExpected = do
97assertFilterConversion msg filterPath docIn docExpected = do
98− docEither <- runIOorExplode $
98+ docEither <- runIOorExplode $ do
99+ setUserDataDir (Just "../data")
99− runLuaFilter (Just "../data") ("lua" </> filterPath) [] docIn
100+ runLuaFilter ("lua" </> filterPath) [] docIn
100 case docEither of
101 case docEither of
101 Left _ -> fail "lua filter failed"
102 Left _ -> fail "lua filter failed"
102 Right docRes -> assertEqual msg docExpected docRes
103 Right docRes -> assertEqual msg docExpected docRes
@@ -105,14 +106,18 @@ roundtripEqual :: (Eq a, Lua.FromLuaStack a, Lua.ToLuaStack a) => a -> IO Bool
105roundtripEqual x = (x ==) <$> roundtripped
106roundtripEqual x = (x ==) <$> roundtripped
106 where
107 where
107 roundtripped :: (Lua.FromLuaStack a, Lua.ToLuaStack a) => IO a
108 roundtripped :: (Lua.FromLuaStack a, Lua.ToLuaStack a) => IO a
108− roundtripped = Lua.runLua $ do
109+ roundtripped = runIOorExplode $ do
109− initLuaState =<< Lua.liftIO (runIOorExplode (luaPackageParams (Just "../data")))
110+ setUserDataDir (Just "../data")
111+ res <- runPandocLua $ do
110− oldSize <- Lua.gettop
112+ oldSize <- Lua.gettop
111− Lua.push x
113+ Lua.push x
112− size <- Lua.gettop
114+ size <- Lua.gettop
113− when (size - oldSize /= 1) $
115+ when (size - oldSize /= 1) $
114− error ("not exactly one additional element on the stack: " ++ show size)
116+ error ("not exactly one additional element on the stack: " ++ show size)
115− res <- Lua.peekEither (-1)
117+ res <- Lua.peekEither (-1)
118+ case res of
119+ Left _ -> error "could not read from stack"
120+ Right y -> return y
116 case res of
121 case res of
117− Left _ -> error "could not read from stack"
122+ Left e -> error (show e)
118 Right y -> return y
123 Right y -> return y