git.iko.soy/typeable/pandoc
on master help sign in
Text.Pandoc.Lua: respect metatable when getting filters

This change makes it possible to define a catch-all function using lua's metatable lookup functionality. function catch_all(el) … end return { setmetatable({}, {__index = function(_) return catch_all end}) } A further effect of this change is that the map with filter functions now only contains functions corresponding to AST element constructors.

commit56fb854ad85dafff2016892bd6d2c5d24423bff0 authorAlbert Krewinkel <albert@zeitkraut.de> dateTue 22 Aug 2017, 22:02 (9 years ago) parent915f201
3 files changed+102−52
src/Text/Pandoc/Lua.hs+76−52 test/Tests/Lua.hs+6 test/lua/metatable-catch-all.lua+20
src/Text/Pandoc/Lua.hs+76−52
@@ -32,50 +32,50 @@ Pandoc lua utils.
3232-}
3333module Text.Pandoc.Lua (LuaException (..), pushPandocModule, runLuaFilter) where
3434
35−import Control.Monad (unless, when, (>=>), mplus)
35+import Control.Monad (mplus, unless, when, (>=>))
3636import Control.Monad.Trans (MonadIO (..))
37−import Data.Data (toConstr, showConstr, dataTypeOf, dataTypeConstrs, Data)
37+import Data.Data (DataType, Data, toConstr, showConstr, dataTypeOf,
38+ dataTypeConstrs)
39+import Data.Foldable (foldrM)
3840import Data.Map (Map)
3941import Data.Maybe (isJust)
4042import Foreign.Lua (Lua, FromLuaStack (peek), LuaException (..), StackIndex,
41− Status(OK), ToLuaStack (push), call, isnil, dofile,
42− getglobal', gettop, isfunction, newtable, openlibs, pcall,
43− peekEither, pop, pushvalue, rawgeti, rawseti, ref,
44− registryindex, runLua, setglobal, throwLuaError)
43+ Status(OK), ToLuaStack (push))
4544import Text.Pandoc.Definition
4645import Text.Pandoc.Lua.PandocModule (pushPandocModule)
4746import Text.Pandoc.Walk (Walkable (walkM))
4847
4948import qualified Data.Map as Map
49+import qualified Foreign.Lua as Lua
5050
5151runLuaFilter :: (MonadIO m)
5252 => Maybe FilePath -> FilePath -> [String] -> Pandoc -> m Pandoc
53−runLuaFilter datadir filterPath args pd = liftIO . runLua $ do
54− openlibs
53+runLuaFilter datadir filterPath args pd = liftIO . Lua.runLua $ do
54+ Lua.openlibs
5555 -- store module in global "pandoc"
5656 pushPandocModule datadir
57− setglobal "pandoc"
58− top <- gettop
59− stat<- dofile filterPath
57+ Lua.setglobal "pandoc"
58+ top <- Lua.gettop
59+ stat<- Lua.dofile filterPath
6060 if stat /= OK
6161 then do
62− luaErrMsg <- peek (-1) <* pop 1
63− throwLuaError luaErrMsg
62+ luaErrMsg <- peek (-1) <* Lua.pop 1
63+ Lua.throwLuaError luaErrMsg
6464 else do
65− newtop <- gettop
65+ newtop <- Lua.gettop
6666 -- Use the implicitly defined global filter if nothing was returned
6767 when (newtop - top < 1) $ pushGlobalFilter
6868 luaFilters <- peek (-1)
6969 push args
70− setglobal "PandocParameters"
70+ Lua.setglobal "PandocParameters"
7171 runAll luaFilters pd
7272
7373pushGlobalFilter :: Lua ()
7474pushGlobalFilter = do
75− newtable
76− getglobal' "pandoc.global_filter"
77− call 0 1
78− rawseti (-2) 1
75+ Lua.newtable
76+ Lua.getglobal' "pandoc.global_filter"
77+ Lua.call 0 1
78+ Lua.rawseti (-2) 1
7979
8080runAll :: [LuaFilter] -> Pandoc -> Lua Pandoc
8181runAll = foldr ((>=>) . walkMWithLuaFilter) return
@@ -85,29 +85,42 @@ walkMWithLuaFilter (LuaFilter fnMap) = walkLua
8585 where
8686 walkLua :: Pandoc -> Lua Pandoc
8787 walkLua =
88− (if hasOneOf (constructorsFor (dataTypeOf (Str [])))
89− then walkM (tryFilter fnMap :: Inline -> Lua Inline)
90− else return)
91− >=>
92− (if hasOneOf (constructorsFor (dataTypeOf (Para [])))
93− then walkM (tryFilter fnMap :: Block -> Lua Block)
94− else return)
95− >=>
96− (case Map.lookup "Meta" fnMap of
97− Just fn -> walkM (\(Pandoc meta blocks) -> do
98− meta' <- runFilterFunction fn meta
99− return $ Pandoc meta' blocks)
100− Nothing -> return)
101− >=>
102− (case Map.lookup "Pandoc" fnMap `mplus` Map.lookup "Doc" fnMap of
103− Just fn -> runFilterFunction fn :: Pandoc -> Lua Pandoc
104− Nothing -> return)
88+ (if hasOneOf inlineFilterNames
89+ then walkM (tryFilter fnMap :: Inline -> Lua Inline)
90+ else return)
91+ >=>
92+ (if hasOneOf blockFilterNames
93+ then walkM (tryFilter fnMap :: Block -> Lua Block)
94+ else return)
95+ >=>
96+ (case Map.lookup "Meta" fnMap of
97+ Just fn -> walkM (\(Pandoc meta blocks) -> do
98+ meta' <- runFilterFunction fn meta
99+ return $ Pandoc meta' blocks)
100+ Nothing -> return)
101+ >=>
102+ (case foldl mplus Nothing (map (`Map.lookup` fnMap) pandocFilterNames) of
103+ Just fn -> runFilterFunction fn :: Pandoc -> Lua Pandoc
104+ Nothing -> return)
105105 hasOneOf = any (\k -> isJust (Map.lookup k fnMap))
106− constructorsFor x = map show (dataTypeConstrs x)
107106
108−type FunctionMap = Map String LuaFilterFunction
109−data LuaFilter = LuaFilter FunctionMap
107+constructorsFor :: DataType -> [String]
108+constructorsFor x = map show (dataTypeConstrs x)
109+
110+inlineFilterNames :: [String]
111+inlineFilterNames = constructorsFor (dataTypeOf (Str []))
112+
113+blockFilterNames :: [String]
114+blockFilterNames = constructorsFor (dataTypeOf (Para []))
110115
116+metaFilterName :: String
117+metaFilterName = "Meta"
118+
119+pandocFilterNames :: [String]
120+pandocFilterNames = ["Pandoc", "Doc"]
121+
122+type FunctionMap = Map String LuaFilterFunction
123+newtype LuaFilter = LuaFilter FunctionMap
111124newtype LuaFilterFunction = LuaFilterFunction { functionIndex :: Int }
112125
113126-- | Try running a filter for the given element
@@ -119,7 +132,18 @@ tryFilter fnMap x =
119132 Just fn -> runFilterFunction fn x
120133
121134instance FromLuaStack LuaFilter where
122− peek idx = LuaFilter <$> peek idx
135+ peek idx =
136+ let constrs = metaFilterName : pandocFilterNames
137+ ++ blockFilterNames
138+ ++ inlineFilterNames
139+ fn c acc = do
140+ Lua.getfield idx c
141+ filterFn <- Lua.tryLua (peek (-1))
142+ Lua.pop 1
143+ return $ case filterFn of
144+ Left _ -> acc
145+ Right f -> (c, f) : acc
146+ in LuaFilter . Map.fromList <$> foldrM fn [] constrs
123147
124148-- | Push a value to the stack via a lua filter function. The filter function is
125149-- called with given element as argument and is expected to return an element.
@@ -130,36 +154,36 @@ runFilterFunction :: (FromLuaStack a, ToLuaStack a)
130154runFilterFunction lf x = do
131155 pushFilterFunction lf
132156 push x
133− z <- pcall 1 1 Nothing
157+ z <- Lua.pcall 1 1 Nothing
134158 if z /= OK
135159 then do
136160 msg <- peek (-1)
137161 let prefix = "Error while running filter function: "
138− throwLuaError $ prefix ++ msg
162+ Lua.throwLuaError $ prefix ++ msg
139163 else do
140− noExplicitFilter <- isnil (-1)
164+ noExplicitFilter <- Lua.isnil (-1)
141165 if noExplicitFilter
142− then pop 1 *> return x
166+ then Lua.pop 1 *> return x
143167 else do
144− mbres <- peekEither (-1)
168+ mbres <- Lua.peekEither (-1)
145169 case mbres of
146− Left err -> throwLuaError
170+ Left err -> Lua.throwLuaError
147171 ("Error while trying to get a filter's return "
148172 ++ "value from lua stack.\n" ++ err)
149− Right res -> res <$ pop 1
173+ Right res -> res <$ Lua.pop 1
150174
151175-- | Push the filter function to the top of the stack.
152176pushFilterFunction :: LuaFilterFunction -> Lua ()
153177pushFilterFunction lf =
154178 -- The function is stored in a lua registry table, retrieve it from there.
155− rawgeti registryindex (functionIndex lf)
179+ Lua.rawgeti Lua.registryindex (functionIndex lf)
156180
157181registerFilterFunction :: StackIndex -> Lua LuaFilterFunction
158182registerFilterFunction idx = do
159− isFn <- isfunction idx
160− unless isFn . throwLuaError $ "Not a function at index " ++ show idx
161− pushvalue idx
162− refIdx <- ref registryindex
183+ isFn <- Lua.isfunction idx
184+ unless isFn . Lua.throwLuaError $ "Not a function at index " ++ show idx
185+ Lua.pushvalue idx
186+ refIdx <- Lua.ref Lua.registryindex
163187 return $ LuaFilterFunction refIdx
164188
165189instance ToLuaStack LuaFilterFunction where
test/Tests/Lua.hs+6
@@ -64,6 +64,12 @@ tests = map (localOption (QuickCheckTests 20))
6464 "single-to-double-quoted.lua"
6565 (doc . para . singleQuoted $ str "simple")
6666 (doc . para . doubleQuoted $ str "simple")
67+
68+ , testCase "Count inlines via metatable catch-all" $
69+ assertFilterConversion "filtering with metatable catch-all failed"
70+ "metatable-catch-all.lua"
71+ (doc . para $ "four words, three spaces")
72+ (doc . para $ str "7")
6773 ]
6874
6975assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
test/lua/metatable-catch-all.luanew file+20
@@ -0,0 +1,20 @@
1+local num_inlines = 0
2+
3+function catch_all(el)
4+ if el.tag and pandoc.Inline.constructor[el.tag] then
5+ num_inlines = num_inlines + 1
6+ end
7+end
8+
9+function Pandoc(blocks, meta)
10+ return pandoc.Pandoc {
11+ pandoc.Para{pandoc.Str(num_inlines)}
12+ }
13+end
14+
15+return {
16+ setmetatable(
17+ {Pandoc = Pandoc},
18+ {__index = function(_) return catch_all end}
19+ )
20+}