2017-03-20 15:17:03 +01:00
|
|
|
{-
|
|
|
|
Copyright © 2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
-}
|
2017-04-12 20:48:44 +02:00
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
2017-03-20 15:17:03 +01:00
|
|
|
{- |
|
|
|
|
Module : Text.Pandoc.Lua
|
|
|
|
Copyright : Copyright © 2017 Albert Krewinkel
|
|
|
|
License : GNU GPL, version 2 or above
|
|
|
|
|
|
|
|
Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
|
|
|
|
Stability : alpha
|
|
|
|
|
|
|
|
Pandoc lua utils.
|
|
|
|
-}
|
2017-04-13 22:57:50 +02:00
|
|
|
module Text.Pandoc.Lua ( runLuaFilter, pushPandocModule ) where
|
2017-03-20 15:17:03 +01:00
|
|
|
|
|
|
|
import Control.Monad ( (>=>), when )
|
|
|
|
import Control.Monad.Trans ( MonadIO(..) )
|
2017-04-14 10:43:44 +02:00
|
|
|
import Data.Map ( Map )
|
2017-03-20 15:17:03 +01:00
|
|
|
import Scripting.Lua ( LuaState, StackValue(..) )
|
|
|
|
import Text.Pandoc.Definition ( Block(..), Inline(..), Pandoc(..) )
|
2017-04-13 22:57:50 +02:00
|
|
|
import Text.Pandoc.Lua.PandocModule ( pushPandocModule )
|
2017-04-02 17:21:22 +02:00
|
|
|
import Text.Pandoc.Lua.StackInstances ()
|
2017-03-20 15:17:03 +01:00
|
|
|
import Text.Pandoc.Walk
|
|
|
|
|
2017-04-14 10:43:44 +02:00
|
|
|
import qualified Data.Map as Map
|
2017-03-20 15:17:03 +01:00
|
|
|
import qualified Scripting.Lua as Lua
|
|
|
|
|
|
|
|
runLuaFilter :: (MonadIO m)
|
|
|
|
=> FilePath -> [String] -> Pandoc -> m Pandoc
|
|
|
|
runLuaFilter filterPath args pd = liftIO $ do
|
2017-04-14 10:43:44 +02:00
|
|
|
lua <- Lua.newstate
|
2017-03-20 15:17:03 +01:00
|
|
|
Lua.openlibs lua
|
2017-04-04 21:51:51 +02:00
|
|
|
-- create table in registry to store filter functions
|
2017-04-14 18:26:42 +02:00
|
|
|
Lua.push lua "PANDOC_FILTER_FUNCTIONS"
|
2017-03-20 15:17:03 +01:00
|
|
|
Lua.newtable lua
|
2017-04-04 21:51:51 +02:00
|
|
|
Lua.rawset lua Lua.registryindex
|
|
|
|
-- store module in global "pandoc"
|
2017-03-20 15:17:03 +01:00
|
|
|
pushPandocModule lua
|
|
|
|
Lua.setglobal lua "pandoc"
|
|
|
|
status <- Lua.loadfile lua filterPath
|
|
|
|
if (status /= 0)
|
|
|
|
then do
|
2017-04-14 10:43:44 +02:00
|
|
|
Just luaErrMsg <- Lua.peek lua 1
|
2017-03-20 15:17:03 +01:00
|
|
|
error luaErrMsg
|
|
|
|
else do
|
|
|
|
Lua.call lua 0 1
|
|
|
|
Just luaFilters <- Lua.peek lua (-1)
|
2017-04-14 10:43:44 +02:00
|
|
|
Lua.push lua args
|
2017-03-20 15:17:03 +01:00
|
|
|
Lua.setglobal lua "PandocParameters"
|
2017-04-14 18:26:42 +02:00
|
|
|
doc <- runAll luaFilters pd
|
2017-03-20 15:17:03 +01:00
|
|
|
Lua.close lua
|
|
|
|
return doc
|
|
|
|
|
|
|
|
runAll :: [LuaFilter] -> Pandoc -> IO Pandoc
|
|
|
|
runAll [] = return
|
|
|
|
runAll (x:xs) = walkMWithLuaFilter x >=> runAll xs
|
|
|
|
|
|
|
|
walkMWithLuaFilter :: LuaFilter -> Pandoc -> IO Pandoc
|
|
|
|
walkMWithLuaFilter (LuaFilter lua inlineFnMap blockFnMap docFnMap) =
|
|
|
|
walkM (execInlineLuaFilter lua inlineFnMap) >=>
|
|
|
|
walkM (execBlockLuaFilter lua blockFnMap) >=>
|
|
|
|
walkM (execDocLuaFilter lua docFnMap)
|
|
|
|
|
2017-04-14 10:43:44 +02:00
|
|
|
type InlineFunctionMap = Map String (LuaFilterFunction Inline)
|
|
|
|
type BlockFunctionMap = Map String (LuaFilterFunction Block)
|
|
|
|
type DocFunctionMap = Map String (LuaFilterFunction Pandoc)
|
2017-03-20 15:17:03 +01:00
|
|
|
data LuaFilter =
|
|
|
|
LuaFilter LuaState InlineFunctionMap BlockFunctionMap DocFunctionMap
|
|
|
|
|
|
|
|
newtype LuaFilterFunction a = LuaFilterFunction { functionIndex :: Int }
|
|
|
|
|
|
|
|
execDocLuaFilter :: LuaState
|
2017-04-14 10:43:44 +02:00
|
|
|
-> Map String (LuaFilterFunction Pandoc)
|
2017-03-20 15:17:03 +01:00
|
|
|
-> Pandoc -> IO Pandoc
|
|
|
|
execDocLuaFilter lua fnMap x = do
|
|
|
|
let docFnName = "Doc"
|
2017-04-14 10:43:44 +02:00
|
|
|
case Map.lookup docFnName fnMap of
|
2017-03-20 15:17:03 +01:00
|
|
|
Nothing -> return x
|
|
|
|
Just fn -> runLuaFilterFunction lua fn x
|
|
|
|
|
|
|
|
execBlockLuaFilter :: LuaState
|
2017-04-14 10:43:44 +02:00
|
|
|
-> Map String (LuaFilterFunction Block)
|
2017-03-20 15:17:03 +01:00
|
|
|
-> Block -> IO Block
|
|
|
|
execBlockLuaFilter lua fnMap x = do
|
2017-04-14 11:21:58 +02:00
|
|
|
let runFn :: PushViaFilterFunction Block a => LuaFilterFunction Block -> a
|
|
|
|
runFn fn = runLuaFilterFunction lua fn
|
|
|
|
let tryFilter :: String -> (LuaFilterFunction Block -> IO Block) -> IO Block
|
|
|
|
tryFilter fnName callFilterFn =
|
|
|
|
case Map.lookup fnName fnMap of
|
|
|
|
Nothing -> return x
|
|
|
|
Just fn -> callFilterFn fn
|
2017-03-20 15:17:03 +01:00
|
|
|
case x of
|
2017-04-14 11:21:58 +02:00
|
|
|
HorizontalRule -> tryFilter "HorizontalRule" runFn
|
|
|
|
Null -> tryFilter "Null" runFn
|
|
|
|
BlockQuote blcks -> tryFilter "BlockQuote" $ \fn -> runFn fn blcks
|
|
|
|
BulletList items -> tryFilter "BulletList" $ \fn -> runFn fn items
|
|
|
|
CodeBlock attr code -> tryFilter "CodeBlock" $ \fn -> runFn fn attr code
|
|
|
|
DefinitionList lst -> tryFilter "DefinitionList" $ \fn -> runFn fn lst
|
|
|
|
Div attr content -> tryFilter "Div" $ \fn -> runFn fn content attr
|
|
|
|
Header lvl attr inlns -> tryFilter "Header" $ \fn -> runFn fn lvl inlns attr
|
|
|
|
LineBlock inlns -> tryFilter "LineBlock" $ \fn -> runFn fn inlns
|
|
|
|
Para inlns -> tryFilter "Para" $ \fn -> runFn fn inlns
|
|
|
|
Plain inlns -> tryFilter "Plain" $ \fn -> runFn fn inlns
|
|
|
|
RawBlock format str -> tryFilter "RawBlock" $ \fn -> runFn fn format str
|
|
|
|
OrderedList (num,sty,delim) items ->
|
|
|
|
tryFilter "OrderedList" $ \fn -> runFn fn items (num,sty,delim)
|
|
|
|
Table capt aligns widths headers rows ->
|
|
|
|
tryFilter "Table" $ \fn -> runFn fn capt aligns widths headers rows
|
2017-03-20 15:17:03 +01:00
|
|
|
|
|
|
|
execInlineLuaFilter :: LuaState
|
2017-04-14 10:43:44 +02:00
|
|
|
-> Map String (LuaFilterFunction Inline)
|
2017-03-20 15:17:03 +01:00
|
|
|
-> Inline -> IO Inline
|
|
|
|
execInlineLuaFilter lua fnMap x = do
|
2017-04-12 20:48:44 +02:00
|
|
|
let runFn :: PushViaFilterFunction Inline a => LuaFilterFunction Inline -> a
|
|
|
|
runFn fn = runLuaFilterFunction lua fn
|
2017-04-14 10:43:44 +02:00
|
|
|
let tryFilter :: String -> (LuaFilterFunction Inline -> IO Inline) -> IO Inline
|
2017-04-12 20:48:44 +02:00
|
|
|
tryFilter fnName callFilterFn =
|
2017-04-14 10:43:44 +02:00
|
|
|
case Map.lookup fnName fnMap of
|
2017-04-12 20:48:44 +02:00
|
|
|
Nothing -> return x
|
|
|
|
Just fn -> callFilterFn fn
|
2017-03-20 15:17:03 +01:00
|
|
|
case x of
|
2017-04-12 20:48:44 +02:00
|
|
|
LineBreak -> tryFilter "LineBreak" runFn
|
|
|
|
SoftBreak -> tryFilter "SoftBreak" runFn
|
|
|
|
Space -> tryFilter "Space" runFn
|
|
|
|
Cite cs lst -> tryFilter "Cite" $ \fn -> runFn fn lst cs
|
|
|
|
Code attr str -> tryFilter "Code" $ \fn -> runFn fn str attr
|
|
|
|
Emph lst -> tryFilter "Emph" $ \fn -> runFn fn lst
|
|
|
|
Math mt lst -> tryFilter "Math" $ \fn -> runFn fn lst mt
|
|
|
|
Note blks -> tryFilter "Note" $ \fn -> runFn fn blks
|
|
|
|
Quoted qt lst -> tryFilter "Quoted" $ \fn -> runFn fn qt lst
|
|
|
|
RawInline f str -> tryFilter "RawInline" $ \fn -> runFn fn f str
|
|
|
|
SmallCaps lst -> tryFilter "SmallCaps" $ \fn -> runFn fn lst
|
|
|
|
Span attr lst -> tryFilter "Span" $ \fn -> runFn fn lst attr
|
|
|
|
Str str -> tryFilter "Str" $ \fn -> runFn fn str
|
|
|
|
Strikeout lst -> tryFilter "Strikeout" $ \fn -> runFn fn lst
|
|
|
|
Strong lst -> tryFilter "Strong" $ \fn -> runFn fn lst
|
|
|
|
Subscript lst -> tryFilter "Subscript" $ \fn -> runFn fn lst
|
|
|
|
Superscript lst -> tryFilter "Superscript" $ \fn -> runFn fn lst
|
|
|
|
Link attr txt (src, tit) -> tryFilter "Link" $
|
|
|
|
\fn -> runFn fn txt src tit attr
|
|
|
|
Image attr alt (src, tit) -> tryFilter "Image" $
|
|
|
|
\fn -> runFn fn alt src tit attr
|
2017-03-20 15:17:03 +01:00
|
|
|
|
|
|
|
instance StackValue LuaFilter where
|
|
|
|
valuetype _ = Lua.TTABLE
|
|
|
|
push = undefined
|
|
|
|
peek lua i = do
|
|
|
|
-- TODO: find a more efficient way of doing this in a typesafe manner.
|
|
|
|
inlineFnMap <- Lua.peek lua i
|
|
|
|
blockFnMap <- Lua.peek lua i
|
|
|
|
docFnMap <- Lua.peek lua i
|
|
|
|
return $ LuaFilter lua <$> inlineFnMap <*> blockFnMap <*> docFnMap
|
|
|
|
|
2017-04-12 20:48:44 +02:00
|
|
|
-- | Helper class for pushing a single value to the stack via a lua function.
|
|
|
|
-- See @pushViaCall@.
|
|
|
|
class PushViaFilterFunction a b where
|
|
|
|
pushViaFilterFunction' :: LuaState -> LuaFilterFunction a -> IO () -> Int -> b
|
|
|
|
|
|
|
|
instance (StackValue a) => PushViaFilterFunction a (IO a) where
|
|
|
|
pushViaFilterFunction' lua lf pushArgs num = do
|
|
|
|
pushFilterFunction lua lf
|
|
|
|
pushArgs
|
|
|
|
Lua.call lua num 1
|
|
|
|
mbres <- Lua.peek lua (-1)
|
|
|
|
case mbres of
|
|
|
|
Nothing -> error $ "Error while trying to get a filter's return "
|
|
|
|
++ "value from lua stack."
|
|
|
|
Just res -> res <$ Lua.pop lua 1
|
|
|
|
|
|
|
|
instance (PushViaFilterFunction a c, StackValue b) =>
|
|
|
|
PushViaFilterFunction a (b -> c) where
|
|
|
|
pushViaFilterFunction' lua lf pushArgs num x =
|
|
|
|
pushViaFilterFunction' lua lf (pushArgs *> push lua x) (num + 1)
|
|
|
|
|
|
|
|
-- | Push an value to the stack via a lua filter function. The function is
|
|
|
|
-- called with all arguments that are passed to this function and is expected to
|
|
|
|
-- return a single value.
|
|
|
|
runLuaFilterFunction :: (StackValue a, PushViaFilterFunction a b)
|
|
|
|
=> LuaState -> LuaFilterFunction a -> b
|
|
|
|
runLuaFilterFunction lua lf = pushViaFilterFunction' lua lf (return ()) 0
|
2017-03-20 15:17:03 +01:00
|
|
|
|
2017-04-04 21:51:51 +02:00
|
|
|
-- | Push the filter function to the top of the stack.
|
2017-03-20 15:17:03 +01:00
|
|
|
pushFilterFunction :: Lua.LuaState -> LuaFilterFunction a -> IO ()
|
|
|
|
pushFilterFunction lua lf = do
|
2017-04-04 21:51:51 +02:00
|
|
|
-- The function is stored in a lua registry table, retrieve it from there.
|
|
|
|
push lua ("PANDOC_FILTER_FUNCTIONS"::String)
|
|
|
|
Lua.rawget lua Lua.registryindex
|
2017-03-20 15:17:03 +01:00
|
|
|
Lua.rawgeti lua (-1) (functionIndex lf)
|
2017-04-04 21:51:51 +02:00
|
|
|
Lua.remove lua (-2) -- remove registry table from stack
|
2017-03-20 15:17:03 +01:00
|
|
|
|
|
|
|
instance StackValue (LuaFilterFunction a) where
|
|
|
|
valuetype _ = Lua.TFUNCTION
|
|
|
|
push lua v = pushFilterFunction lua v
|
|
|
|
peek lua i = do
|
|
|
|
isFn <- Lua.isfunction lua i
|
|
|
|
when (not isFn) (error $ "Not a function at index " ++ (show i))
|
|
|
|
Lua.pushvalue lua i
|
2017-04-04 21:51:51 +02:00
|
|
|
push lua ("PANDOC_FILTER_FUNCTIONS"::String)
|
|
|
|
Lua.rawget lua Lua.registryindex
|
2017-03-20 15:17:03 +01:00
|
|
|
len <- Lua.objlen lua (-1)
|
|
|
|
Lua.insert lua (-2)
|
|
|
|
Lua.rawseti lua (-2) (len + 1)
|
|
|
|
Lua.pop lua 1
|
|
|
|
return . Just $ LuaFilterFunction (len + 1)
|