git.iko.soy/typeable/pandoc
on master help sign in
Lua filters (#3514)

* Add `--lua-filter` option. This works like `--filter` but takes pathnames of special lua filters and uses the lua interpreter baked into pandoc, so that no external interpreter is needed. Note that lua filters are all applied after regular filters, regardless of their position on the command line. * Add Text.Pandoc.Lua, exporting `runLuaFilter`. Add `pandoc.lua` to data files. * Add private module Text.Pandoc.Lua.PandocModule to supply the default lua module. * Add Tests.Lua to tests. * Add data/pandoc.lua, the lua module pandoc imports when processing its lua filters. * Document in MANUAL.txt.

commitf2f6851713674545e2f303b95589cbaff8e6a6b9 authorAlbert Krewinkel <albert+github@zeitkraut.de> dateMon 20 Mar 2017, 15:17 (10 years ago) parentb010a8c
13 files changed+528−1
MANUAL.txt+25 data/pandoc.lua+144 pandoc.cabal+7 pandoc.hs−1 src/Text/Pandoc/App.hs+16 src/Text/Pandoc/Lua.hs+226 src/Text/Pandoc/Lua/PandocModule.hs+47 stack.yaml+1 test/Tests/Lua.hs+34 test/lua/hello-world-doc.lua+10 test/lua/plain-to-para.lua+6 test/lua/strmacro.lua+10 test/test-pandoc.hs+2
MANUAL.txt+25
@@ -457,6 +457,31 @@ Reader options
457457
458458 3. `$PATH` (executable only)
459459
460+`--lua-filter=`*SCRIPT*
461+
462+: Transform the document in a similar fashion as JSON filters (see
463+ `--filter`), but use pandoc's build-in lua filtering system. The given
464+ lua script is expected to return a list of lua filters which will be
465+ applied in order. Each lua filter must contain element-transforming
466+ functions indexed by the name of the AST element on which the filter
467+ function should be applied.
468+
469+ The `pandoc` lua module provides helper functions for element
470+ creation. It is always loaded into the script's lua environment.
471+
472+ The following is an example lua script for macro-expansion:
473+
474+ function expand_hello_world(inline)
475+ if inline.c == '{{helloworld}}' then
476+ return pandoc.Emph{ pandoc.Str "Hello, World" }
477+ else
478+ return inline
479+ end
480+ end
481+
482+ return {{Str = expand_hello_world}}
483+
484+
460485`-M` *KEY*[`=`*VAL*], `--metadata=`*KEY*[`:`*VAL*]
461486
462487: Set the metadata field *KEY* to the value *VAL*. A value specified
data/pandoc.luanew file+144
@@ -0,0 +1,144 @@
1+--[[
2+pandoc.lua
3+
4+Copyright (c) 2017 Albert Krewinkel
5+
6+Permission to use, copy, modify, and/or distribute this software for any purpose
7+with or without fee is hereby granted, provided that the above copyright notice
8+and this permission notice appear in all copies.
9+
10+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
16+THIS SOFTWARE.
17+]]
18+
19+--- The module
20+local M = {
21+ _version = "0.1.0"
22+}
23+
24+--- Create a new set of attributes (Attr).
25+function M.Attributes(id, classes, key_values)
26+ return {id, classes, key_values}
27+end
28+
29+local Element = {}
30+--- Create a new element subtype
31+function Element:make_subtype(o)
32+ o = o or {}
33+ setmetatable(o, self)
34+ self.__index = self
35+ return o
36+end
37+--- Create a new element given its tag and arguments
38+function Element:new(tag, ...)
39+ local element = { t = tag }
40+ local content = {...}
41+ -- special case for unary constructors
42+ if #content == 1 then
43+ element.c = content[1]
44+ -- Don't set 'c' field if no further arguments were given. This is important
45+ -- for nullary constructors like `Space` and `HorizontalRule`.
46+ elseif #content > 0 then
47+ element.c = content
48+ end
49+ setmetatable(element, self)
50+ self.__index = self
51+ return element
52+end
53+
54+local function Doc(blocks, meta)
55+ return {
56+ ["blocks"] = blocks,
57+ ["meta"] = meta,
58+ ["pandoc-api-version"] = {1,17,0,5},
59+ }
60+end
61+
62+local Inline = Element:make_subtype{}
63+local Block = Element:make_subtype{}
64+
65+M.block_types = {
66+ "BlockQuote",
67+ "BulletList",
68+ "CodeBlock",
69+ "DefinitionList",
70+ "Div",
71+ "Header",
72+ "HorizontalRule",
73+ "HorizontalRule",
74+ "LineBlock",
75+ "Null",
76+ "OrderedList",
77+ "Para",
78+ "Plain",
79+ "RawBlock",
80+ "Table",
81+}
82+
83+M.inline_types = {
84+ "Cite",
85+ "Code",
86+ "DisplayMath",
87+ "DoubleQuoted",
88+ "Emph",
89+ "Image",
90+ "InlineMath",
91+ "LineBreak",
92+ "Link",
93+ "Math",
94+ "Note",
95+ "Quoted",
96+ "RawInline",
97+ "SingleQuoted",
98+ "SmallCaps",
99+ "SoftBreak",
100+ "Space",
101+ "Span",
102+ "Str",
103+ "Strikeout",
104+ "Strong",
105+ "Subscript",
106+ "Superscript"
107+}
108+
109+for _, block_type in pairs(M.block_types) do
110+ M[block_type] = function(...)
111+ return Block:new(block_type, ...)
112+ end
113+end
114+
115+for _, inline_type in pairs(M.inline_types) do
116+ M[inline_type] = function(...)
117+ return Inline:new(inline_type, ...)
118+ end
119+end
120+
121+--- Arrays to provide fast lookup of element types
122+local set_of_inline_types = {}
123+local set_of_block_types = {}
124+
125+for i = 1, #M.inline_types do
126+ set_of_inline_types[M.inline_types[i]] = true
127+end
128+for i = 1, #M.block_types do
129+ set_of_block_types[M.block_types[i]] = true
130+end
131+
132+function M.global_filter()
133+ local res = {}
134+ for k, v in pairs(_G) do
135+ if set_of_inline_types[k] or set_of_block_types[k] or k == "Doc" then
136+ res[k] = v
137+ end
138+ end
139+ return res
140+end
141+
142+M["Doc"] = Doc
143+
144+return M
pandoc.cabal+7
@@ -104,6 +104,8 @@ Data-Files:
104104 data/abbreviations
105105 -- sample lua custom writer
106106 data/sample.lua
107+ -- pandoc lua module
108+ data/pandoc.lua
107109 -- bash completion template
108110 data/bash_completion.tpl
109111 -- documentation
@@ -232,6 +234,7 @@ Extra-Source-Files:
232234 test/odt/odt/*.odt
233235 test/odt/markdown/*.md
234236 test/odt/native/*.native
237+ test/lua/strmacro.lua
235238Source-repository head
236239 type: git
237240 location: git://github.com/jgm/pandoc.git
@@ -282,6 +285,7 @@ Library
282285 pandoc-types >= 1.17 && < 1.18,
283286 aeson >= 0.7 && < 1.2,
284287 aeson-pretty >= 0.8 && < 0.9,
288+ hslua-aeson >= 0.1.0.2 && < 1,
285289 tagsoup >= 0.13.7 && < 0.15,
286290 base64-bytestring >= 0.1 && < 1.1,
287291 zlib >= 0.5 && < 0.7,
@@ -396,6 +400,7 @@ Library
396400 Text.Pandoc.Writers.Muse,
397401 Text.Pandoc.Writers.Math,
398402 Text.Pandoc.Writers.Shared,
403+ Text.Pandoc.Lua,
399404 Text.Pandoc.PDF,
400405 Text.Pandoc.UTF8,
401406 Text.Pandoc.Templates,
@@ -434,6 +439,7 @@ Library
434439 Text.Pandoc.Readers.Org.ParserState,
435440 Text.Pandoc.Readers.Org.Parsing,
436441 Text.Pandoc.Readers.Org.Shared,
442+ Text.Pandoc.Lua.PandocModule,
437443 Text.Pandoc.CSS,
438444 Text.Pandoc.UUID,
439445 Text.Pandoc.Slides,
@@ -522,6 +528,7 @@ Test-Suite test-pandoc
522528 Other-Modules: Tests.Old
523529 Tests.Command
524530 Tests.Helpers
531+ Tests.Lua
525532 Tests.Shared
526533 Tests.Readers.LaTeX
527534 Tests.Readers.HTML
pandoc.hs−1
@@ -37,4 +37,3 @@ import Text.Pandoc.App (convertWithOpts, defaultOpts, options, parseOptions)
3737
3838main :: IO ()
3939main = parseOptions options defaultOpts >>= convertWithOpts
40−
src/Text/Pandoc/App.hs+16
@@ -69,6 +69,7 @@ import Text.Pandoc
6969import Text.Pandoc.Builder (setMeta)
7070import Text.Pandoc.Class (PandocIO, getLog, withMediaBag)
7171import Text.Pandoc.Highlighting (highlightingStyles)
72+import Text.Pandoc.Lua ( runLuaFilter )
7273import Text.Pandoc.MediaBag (MediaBag, extractMediaBag, mediaDirectory)
7374import Text.Pandoc.PDF (makePDF)
7475import Text.Pandoc.Process (pipeProcess)
@@ -389,6 +390,7 @@ convertWithOpts opts = do
389390 doc' <- (maybe return (extractMedia media) (optExtractMedia opts) >=>
390391 return . flip (foldr addMetadata) (optMetadata opts) >=>
391392 applyTransforms transforms >=>
393+ applyLuaFilters datadir (optLuaFilters opts) [format] >=>
392394 applyFilters datadir filters' [format]) doc
393395
394396 case writer of
@@ -514,6 +516,7 @@ data Opt = Opt
514516 , optWrapText :: WrapOption -- ^ Options for wrapping text
515517 , optColumns :: Int -- ^ Line length in characters
516518 , optFilters :: [FilePath] -- ^ Filters to apply
519+ , optLuaFilters :: [FilePath] -- ^ Lua filters to apply
517520 , optEmailObfuscation :: ObfuscationMethod
518521 , optIdentifierPrefix :: String
519522 , optIndentedCodeClasses :: [String] -- ^ Default classes for indented code blocks
@@ -580,6 +583,7 @@ defaultOpts = Opt
580583 , optWrapText = WrapAuto
581584 , optColumns = 72
582585 , optFilters = []
586+ , optLuaFilters = []
583587 , optEmailObfuscation = NoObfuscation
584588 , optIdentifierPrefix = ""
585589 , optIndentedCodeClasses = []
@@ -725,6 +729,12 @@ expandFilterPath mbDatadir fp = liftIO $ do
725729 else return fp
726730 _ -> return fp
727731
732+applyLuaFilters :: MonadIO m
733+ => Maybe FilePath -> [FilePath] -> [String] -> Pandoc -> m Pandoc
734+applyLuaFilters mbDatadir filters args d = do
735+ expandedFilters <- mapM (expandFilterPath mbDatadir) filters
736+ foldrM ($) d $ map (flip runLuaFilter args) expandedFilters
737+
728738applyFilters :: MonadIO m
729739 => Maybe FilePath -> [FilePath] -> [String] -> Pandoc -> m Pandoc
730740applyFilters mbDatadir filters args d = do
@@ -814,6 +824,12 @@ options =
814824 "PROGRAM")
815825 "" -- "External JSON filter"
816826
827+ , Option "" ["lua-filter"]
828+ (ReqArg
829+ (\arg opt -> return opt { optLuaFilters = arg : optLuaFilters opt })
830+ "SCRIPTPATH")
831+ "" -- "Lua filter"
832+
817833 , Option "p" ["preserve-tabs"]
818834 (NoArg
819835 (\opt -> return opt { optPreserveTabs = True }))
src/Text/Pandoc/Lua.hsnew file+226
@@ -0,0 +1,226 @@
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+{-# LANGUAGE FlexibleContexts #-}
19+{-# LANGUAGE LambdaCase #-}
20+{-# LANGUAGE OverloadedStrings #-}
21+{-# LANGUAGE ScopedTypeVariables #-}
22+{-# OPTIONS_GHC -fno-warn-orphans #-}
23+{- |
24+ Module : Text.Pandoc.Lua
25+ Copyright : Copyright © 2017 Albert Krewinkel
26+ License : GNU GPL, version 2 or above
27+
28+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
29+ Stability : alpha
30+
31+Pandoc lua utils.
32+-}
33+module Text.Pandoc.Lua ( runLuaFilter ) where
34+
35+import Control.Monad ( (>=>), when )
36+import Control.Monad.Trans ( MonadIO(..) )
37+import Data.Aeson ( FromJSON(..), ToJSON(..), Result(..), Value, fromJSON )
38+import Data.HashMap.Lazy ( HashMap )
39+import Data.Text ( Text, pack, unpack )
40+import Data.Text.Encoding ( decodeUtf8 )
41+import Scripting.Lua ( LuaState, StackValue(..) )
42+import Scripting.Lua.Aeson ()
43+import Text.Pandoc.Definition ( Block(..), Inline(..), Pandoc(..) )
44+import Text.Pandoc.Lua.PandocModule
45+import Text.Pandoc.Walk
46+
47+import qualified Data.HashMap.Lazy as HashMap
48+import qualified Scripting.Lua as Lua
49+import qualified Scripting.Lua as LuaAeson
50+
51+runLuaFilter :: (MonadIO m)
52+ => FilePath -> [String] -> Pandoc -> m Pandoc
53+runLuaFilter filterPath args pd = liftIO $ do
54+ lua <- LuaAeson.newstate
55+ Lua.openlibs lua
56+ Lua.newtable lua
57+ Lua.setglobal lua "PANDOC_FILTER_FUNCTIONS" -- hack, store functions here
58+ pushPandocModule lua
59+ Lua.setglobal lua "pandoc"
60+ status <- Lua.loadfile lua filterPath
61+ if (status /= 0)
62+ then do
63+ luaErrMsg <- unpack . decodeUtf8 <$> Lua.tostring lua 1
64+ error luaErrMsg
65+ else do
66+ Lua.call lua 0 1
67+ Just luaFilters <- Lua.peek lua (-1)
68+ Lua.push lua (map pack args)
69+ Lua.setglobal lua "PandocParameters"
70+ doc <- runAll luaFilters >=> luaFilter lua "filter_doc" $ pd
71+ Lua.close lua
72+ return doc
73+
74+runAll :: [LuaFilter] -> Pandoc -> IO Pandoc
75+runAll [] = return
76+runAll (x:xs) = walkMWithLuaFilter x >=> runAll xs
77+
78+luaFilter :: Lua.LuaState -> String -> Pandoc -> IO Pandoc
79+luaFilter lua luaFn x = do
80+ fnExists <- isLuaFunction lua luaFn
81+ if fnExists
82+ then walkM (Lua.callfunc lua luaFn :: Pandoc -> IO Pandoc) x
83+ else return x
84+
85+walkMWithLuaFilter :: LuaFilter -> Pandoc -> IO Pandoc
86+walkMWithLuaFilter (LuaFilter lua inlineFnMap blockFnMap docFnMap) =
87+ walkM (execInlineLuaFilter lua inlineFnMap) >=>
88+ walkM (execBlockLuaFilter lua blockFnMap) >=>
89+ walkM (execDocLuaFilter lua docFnMap)
90+
91+type InlineFunctionMap = HashMap Text (LuaFilterFunction Inline)
92+type BlockFunctionMap = HashMap Text (LuaFilterFunction Block)
93+type DocFunctionMap = HashMap Text (LuaFilterFunction Pandoc)
94+data LuaFilter =
95+ LuaFilter LuaState InlineFunctionMap BlockFunctionMap DocFunctionMap
96+
97+newtype LuaFilterFunction a = LuaFilterFunction { functionIndex :: Int }
98+
99+execDocLuaFilter :: LuaState
100+ -> HashMap Text (LuaFilterFunction Pandoc)
101+ -> Pandoc -> IO Pandoc
102+execDocLuaFilter lua fnMap x = do
103+ let docFnName = "Doc"
104+ case HashMap.lookup docFnName fnMap of
105+ Nothing -> return x
106+ Just fn -> runLuaFilterFunction lua fn x
107+
108+execBlockLuaFilter :: LuaState
109+ -> HashMap Text (LuaFilterFunction Block)
110+ -> Block -> IO Block
111+execBlockLuaFilter lua fnMap x = do
112+ let filterOrId constr = case HashMap.lookup constr fnMap of
113+ Nothing -> return x
114+ Just fn -> runLuaFilterFunction lua fn x
115+ case x of
116+ Plain _ -> filterOrId "Plain"
117+ Para _ -> filterOrId "Para"
118+ LineBlock _ -> filterOrId "LineBlock"
119+ CodeBlock _ _ -> filterOrId "CodeBlock"
120+ RawBlock _ _ -> filterOrId "RawBlock"
121+ BlockQuote _ -> filterOrId "BlockQuote"
122+ OrderedList _ _ -> filterOrId "OrderedList"
123+ BulletList _ -> filterOrId "BulletList"
124+ DefinitionList _ -> filterOrId "DefinitionList"
125+ Header _ _ _ -> filterOrId "Header"
126+ HorizontalRule -> filterOrId "HorizontalRule"
127+ Table _ _ _ _ _ -> filterOrId "Table"
128+ Div _ _ -> filterOrId "Div"
129+ Null -> filterOrId "Null"
130+
131+execInlineLuaFilter :: LuaState
132+ -> HashMap Text (LuaFilterFunction Inline)
133+ -> Inline -> IO Inline
134+execInlineLuaFilter lua fnMap x = do
135+ let filterOrId constr = case HashMap.lookup constr fnMap of
136+ Nothing -> return x
137+ Just fn -> runLuaFilterFunction lua fn x
138+ case x of
139+ Cite _ _ -> filterOrId "Cite"
140+ Code _ _ -> filterOrId "Code"
141+ Emph _ -> filterOrId "Emph"
142+ Image _ _ _ -> filterOrId "Image"
143+ LineBreak -> filterOrId "LineBreak"
144+ Link _ _ _ -> filterOrId "Link"
145+ Math _ _ -> filterOrId "Math"
146+ Note _ -> filterOrId "Note"
147+ Quoted _ _ -> filterOrId "Quoted"
148+ RawInline _ _ -> filterOrId "RawInline"
149+ SmallCaps _ -> filterOrId "SmallCaps"
150+ SoftBreak -> filterOrId "SoftBreak"
151+ Space -> filterOrId "Space"
152+ Span _ _ -> filterOrId "Span"
153+ Str _ -> filterOrId "Str"
154+ Strikeout _ -> filterOrId "Strikeout"
155+ Strong _ -> filterOrId "Strong"
156+ Subscript _ -> filterOrId "Subscript"
157+ Superscript _ -> filterOrId "Superscript"
158+
159+instance StackValue LuaFilter where
160+ valuetype _ = Lua.TTABLE
161+ push = undefined
162+ peek lua i = do
163+ -- TODO: find a more efficient way of doing this in a typesafe manner.
164+ inlineFnMap <- Lua.peek lua i
165+ blockFnMap <- Lua.peek lua i
166+ docFnMap <- Lua.peek lua i
167+ return $ LuaFilter lua <$> inlineFnMap <*> blockFnMap <*> docFnMap
168+
169+runLuaFilterFunction :: (StackValue a)
170+ => LuaState -> LuaFilterFunction a -> a -> IO a
171+runLuaFilterFunction lua lf inline = do
172+ pushFilterFunction lua lf
173+ Lua.push lua inline
174+ Lua.call lua 1 1
175+ Just res <- Lua.peek lua (-1)
176+ Lua.pop lua 1
177+ return res
178+
179+-- FIXME: use registry
180+pushFilterFunction :: Lua.LuaState -> LuaFilterFunction a -> IO ()
181+pushFilterFunction lua lf = do
182+ Lua.getglobal lua "PANDOC_FILTER_FUNCTIONS"
183+ Lua.rawgeti lua (-1) (functionIndex lf)
184+ Lua.remove lua (-2) -- remove global from stack
185+
186+instance StackValue (LuaFilterFunction a) where
187+ valuetype _ = Lua.TFUNCTION
188+ push lua v = pushFilterFunction lua v
189+ peek lua i = do
190+ isFn <- Lua.isfunction lua i
191+ when (not isFn) (error $ "Not a function at index " ++ (show i))
192+ Lua.pushvalue lua i
193+ Lua.getglobal lua "PANDOC_FILTER_FUNCTIONS"
194+ len <- Lua.objlen lua (-1)
195+ Lua.insert lua (-2)
196+ Lua.rawseti lua (-2) (len + 1)
197+ Lua.pop lua 1
198+ return . Just $ LuaFilterFunction (len + 1)
199+
200+
201+isLuaFunction :: Lua.LuaState -> String -> IO Bool
202+isLuaFunction lua fnName = do
203+ Lua.getglobal lua fnName
204+ res <- Lua.isfunction lua (-1)
205+ Lua.pop lua (-1)
206+ return res
207+
208+maybeFromJson :: (FromJSON a) => Maybe Value -> Maybe a
209+maybeFromJson mv = fromJSON <$> mv >>= \case
210+ Success x -> Just x
211+ _ -> Nothing
212+
213+instance StackValue Pandoc where
214+ push lua = Lua.push lua . toJSON
215+ peek lua i = maybeFromJson <$> peek lua i
216+ valuetype _ = Lua.TTABLE
217+
218+instance StackValue Block where
219+ push lua = Lua.push lua . toJSON
220+ peek lua i = maybeFromJson <$> peek lua i
221+ valuetype _ = Lua.TTABLE
222+
223+instance StackValue Inline where
224+ push lua = Lua.push lua . toJSON
225+ peek lua i = maybeFromJson <$> peek lua i
226+ valuetype _ = Lua.TTABLE
src/Text/Pandoc/Lua/PandocModule.hsnew file+47
@@ -0,0 +1,47 @@
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.PandocModule
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+Pandoc module for lua.
27+-}
28+module Text.Pandoc.Lua.PandocModule ( pushPandocModule ) where
29+
30+import Data.ByteString.Char8 ( unpack )
31+import Scripting.Lua ( LuaState, loadstring, call)
32+import Text.Pandoc.Shared ( readDataFile )
33+
34+
35+-- | Push the "pandoc" on the lua stack.
36+pushPandocModule :: LuaState -> IO ()
37+pushPandocModule lua = do
38+ script <- pandocModuleScript
39+ status <- loadstring lua script "cn"
40+ if (status /= 0)
41+ then return ()
42+ else do
43+ call lua 0 1
44+
45+-- | Get the string representation of the pandoc module
46+pandocModuleScript :: IO String
47+pandocModuleScript = unpack <$> readDataFile Nothing "pandoc.lua"
stack.yaml+1
@@ -8,6 +8,7 @@ flags:
88packages:
99- '.'
1010extra-deps:
11+- hslua-aeson-0.1.0.2
1112- skylighting-0.3.1
1213- texmath-0.9.3
1314resolver: lts-8.4
test/Tests/Lua.hsnew file+34
@@ -0,0 +1,34 @@
1+{-# Language OverloadedStrings #-}
2+module Tests.Lua ( tests ) where
3+
4+import System.FilePath ((</>))
5+import Test.Tasty (TestTree)
6+import Test.Tasty.HUnit (Assertion, assertEqual, testCase)
7+import Text.Pandoc.Builder
8+import Text.Pandoc.Lua
9+
10+tests :: [TestTree]
11+tests =
12+ [ testCase "macro expansion via filter" $
13+ assertFilterConversion "a '{{helloworld}}' string is expanded"
14+ "strmacro.lua"
15+ (doc . para $ str "{{helloworld}}")
16+ (doc . para . emph $ str "Hello, World")
17+
18+ , testCase "convert all plains to paras" $
19+ assertFilterConversion "plains become para"
20+ "plain-to-para.lua"
21+ (doc $ bulletList [plain (str "alfa"), plain (str "bravo")])
22+ (doc $ bulletList [para (str "alfa"), para (str "bravo")])
23+
24+ , testCase "make hello world document" $
25+ assertFilterConversion "Document contains 'Hello, World!'"
26+ "hello-world-doc.lua"
27+ (doc . para $ str "Hey!" <> linebreak <> str "What's up?")
28+ (doc . para $ str "Hello," <> space <> str "World!")
29+ ]
30+
31+assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
32+assertFilterConversion msg filterPath docIn docExpected = do
33+ docRes <- runLuaFilter ("lua" </> filterPath) [] docIn
34+ assertEqual msg docExpected docRes
test/lua/hello-world-doc.luanew file+10
@@ -0,0 +1,10 @@
1+return {
2+ {
3+ Doc = function(doc)
4+ local meta = {}
5+ local hello = { pandoc.Str "Hello,", pandoc.Space(), pandoc.Str "World!" }
6+ local blocks = { pandoc.Para(hello) }
7+ return pandoc.Doc(blocks, meta)
8+ end
9+ }
10+}
test/lua/plain-to-para.luanew file+6
@@ -0,0 +1,6 @@
1+return {
2+ { Plain = function (blk)
3+ return pandoc.Para(blk.c)
4+ end,
5+ }
6+}
test/lua/strmacro.luanew file+10
@@ -0,0 +1,10 @@
1+return {
2+ { Str = function (inline)
3+ if inline.c == "{{helloworld}}" then
4+ return pandoc.Emph {pandoc.Str "Hello, World"}
5+ else
6+ return inline
7+ end
8+ end,
9+ }
10+}
test/test-pandoc.hs+2
@@ -5,6 +5,7 @@ module Main where
55import GHC.IO.Encoding
66import Test.Tasty
77import qualified Tests.Command
8+import qualified Tests.Lua
89import qualified Tests.Old
910import qualified Tests.Readers.Docx
1011import qualified Tests.Readers.EPUB
@@ -61,6 +62,7 @@ tests = testGroup "pandoc tests" [ Tests.Command.tests
6162 , testGroup "Txt2Tags" Tests.Readers.Txt2Tags.tests
6263 , testGroup "EPUB" Tests.Readers.EPUB.tests
6364 ]
65+ , testGroup "Lua filters" Tests.Lua.tests
6466 ]
6567
6668main :: IO ()