git.iko.soy/typeable/pandoc
on master help sign in
Lua filters (#3514)
f2f6851Albert Krewinkel
MANUAL.txt+25
@@ -457,6 +457,31 @@ Reader options
457
457
458 3. `$PATH` (executable only)
458 3. `$PATH` (executable only)
459
459
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+
460`-M` *KEY*[`=`*VAL*], `--metadata=`*KEY*[`:`*VAL*]
485`-M` *KEY*[`=`*VAL*], `--metadata=`*KEY*[`:`*VAL*]
461
486
462: Set the metadata field *KEY* to the value *VAL*. A value specified
487: 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:
104 data/abbreviations
104 data/abbreviations
105 -- sample lua custom writer
105 -- sample lua custom writer
106 data/sample.lua
106 data/sample.lua
107+ -- pandoc lua module
108+ data/pandoc.lua
107 -- bash completion template
109 -- bash completion template
108 data/bash_completion.tpl
110 data/bash_completion.tpl
109 -- documentation
111 -- documentation
@@ -232,6 +234,7 @@ Extra-Source-Files:
232 test/odt/odt/*.odt
234 test/odt/odt/*.odt
233 test/odt/markdown/*.md
235 test/odt/markdown/*.md
234 test/odt/native/*.native
236 test/odt/native/*.native
237+ test/lua/strmacro.lua
235Source-repository head
238Source-repository head
236 type: git
239 type: git
237 location: git://github.com/jgm/pandoc.git
240 location: git://github.com/jgm/pandoc.git
@@ -282,6 +285,7 @@ Library
282 pandoc-types >= 1.17 && < 1.18,
285 pandoc-types >= 1.17 && < 1.18,
283 aeson >= 0.7 && < 1.2,
286 aeson >= 0.7 && < 1.2,
284 aeson-pretty >= 0.8 && < 0.9,
287 aeson-pretty >= 0.8 && < 0.9,
288+ hslua-aeson >= 0.1.0.2 && < 1,
285 tagsoup >= 0.13.7 && < 0.15,
289 tagsoup >= 0.13.7 && < 0.15,
286 base64-bytestring >= 0.1 && < 1.1,
290 base64-bytestring >= 0.1 && < 1.1,
287 zlib >= 0.5 && < 0.7,
291 zlib >= 0.5 && < 0.7,
@@ -396,6 +400,7 @@ Library
396 Text.Pandoc.Writers.Muse,
400 Text.Pandoc.Writers.Muse,
397 Text.Pandoc.Writers.Math,
401 Text.Pandoc.Writers.Math,
398 Text.Pandoc.Writers.Shared,
402 Text.Pandoc.Writers.Shared,
403+ Text.Pandoc.Lua,
399 Text.Pandoc.PDF,
404 Text.Pandoc.PDF,
400 Text.Pandoc.UTF8,
405 Text.Pandoc.UTF8,
401 Text.Pandoc.Templates,
406 Text.Pandoc.Templates,
@@ -434,6 +439,7 @@ Library
434 Text.Pandoc.Readers.Org.ParserState,
439 Text.Pandoc.Readers.Org.ParserState,
435 Text.Pandoc.Readers.Org.Parsing,
440 Text.Pandoc.Readers.Org.Parsing,
436 Text.Pandoc.Readers.Org.Shared,
441 Text.Pandoc.Readers.Org.Shared,
442+ Text.Pandoc.Lua.PandocModule,
437 Text.Pandoc.CSS,
443 Text.Pandoc.CSS,
438 Text.Pandoc.UUID,
444 Text.Pandoc.UUID,
439 Text.Pandoc.Slides,
445 Text.Pandoc.Slides,
@@ -522,6 +528,7 @@ Test-Suite test-pandoc
522 Other-Modules: Tests.Old
528 Other-Modules: Tests.Old
523 Tests.Command
529 Tests.Command
524 Tests.Helpers
530 Tests.Helpers
531+ Tests.Lua
525 Tests.Shared
532 Tests.Shared
526 Tests.Readers.LaTeX
533 Tests.Readers.LaTeX
527 Tests.Readers.HTML
534 Tests.Readers.HTML
pandoc.hs−1
@@ -37,4 +37,3 @@ import Text.Pandoc.App (convertWithOpts, defaultOpts, options, parseOptions)
37
37
38main :: IO ()
38main :: IO ()
39main = parseOptions options defaultOpts >>= convertWithOpts
39main = parseOptions options defaultOpts >>= convertWithOpts
40−
src/Text/Pandoc/App.hs+16
@@ -69,6 +69,7 @@ import Text.Pandoc
69import Text.Pandoc.Builder (setMeta)
69import Text.Pandoc.Builder (setMeta)
70import Text.Pandoc.Class (PandocIO, getLog, withMediaBag)
70import Text.Pandoc.Class (PandocIO, getLog, withMediaBag)
71import Text.Pandoc.Highlighting (highlightingStyles)
71import Text.Pandoc.Highlighting (highlightingStyles)
72+import Text.Pandoc.Lua ( runLuaFilter )
72import Text.Pandoc.MediaBag (MediaBag, extractMediaBag, mediaDirectory)
73import Text.Pandoc.MediaBag (MediaBag, extractMediaBag, mediaDirectory)
73import Text.Pandoc.PDF (makePDF)
74import Text.Pandoc.PDF (makePDF)
74import Text.Pandoc.Process (pipeProcess)
75import Text.Pandoc.Process (pipeProcess)
@@ -389,6 +390,7 @@ convertWithOpts opts = do
389 doc' <- (maybe return (extractMedia media) (optExtractMedia opts) >=>
390 doc' <- (maybe return (extractMedia media) (optExtractMedia opts) >=>
390 return . flip (foldr addMetadata) (optMetadata opts) >=>
391 return . flip (foldr addMetadata) (optMetadata opts) >=>
391 applyTransforms transforms >=>
392 applyTransforms transforms >=>
393+ applyLuaFilters datadir (optLuaFilters opts) [format] >=>
392 applyFilters datadir filters' [format]) doc
394 applyFilters datadir filters' [format]) doc
393
395
394 case writer of
396 case writer of
@@ -514,6 +516,7 @@ data Opt = Opt
514 , optWrapText :: WrapOption -- ^ Options for wrapping text
516 , optWrapText :: WrapOption -- ^ Options for wrapping text
515 , optColumns :: Int -- ^ Line length in characters
517 , optColumns :: Int -- ^ Line length in characters
516 , optFilters :: [FilePath] -- ^ Filters to apply
518 , optFilters :: [FilePath] -- ^ Filters to apply
519+ , optLuaFilters :: [FilePath] -- ^ Lua filters to apply
517 , optEmailObfuscation :: ObfuscationMethod
520 , optEmailObfuscation :: ObfuscationMethod
518 , optIdentifierPrefix :: String
521 , optIdentifierPrefix :: String
519 , optIndentedCodeClasses :: [String] -- ^ Default classes for indented code blocks
522 , optIndentedCodeClasses :: [String] -- ^ Default classes for indented code blocks
@@ -580,6 +583,7 @@ defaultOpts = Opt
580 , optWrapText = WrapAuto
583 , optWrapText = WrapAuto
581 , optColumns = 72
584 , optColumns = 72
582 , optFilters = []
585 , optFilters = []
586+ , optLuaFilters = []
583 , optEmailObfuscation = NoObfuscation
587 , optEmailObfuscation = NoObfuscation
584 , optIdentifierPrefix = ""
588 , optIdentifierPrefix = ""
585 , optIndentedCodeClasses = []
589 , optIndentedCodeClasses = []
@@ -725,6 +729,12 @@ expandFilterPath mbDatadir fp = liftIO $ do
725 else return fp
729 else return fp
726 _ -> return fp
730 _ -> return fp
727
731
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+
728applyFilters :: MonadIO m
738applyFilters :: MonadIO m
729 => Maybe FilePath -> [FilePath] -> [String] -> Pandoc -> m Pandoc
739 => Maybe FilePath -> [FilePath] -> [String] -> Pandoc -> m Pandoc
730applyFilters mbDatadir filters args d = do
740applyFilters mbDatadir filters args d = do
@@ -814,6 +824,12 @@ options =
814 "PROGRAM")
824 "PROGRAM")
815 "" -- "External JSON filter"
825 "" -- "External JSON filter"
816
826
827+ , Option "" ["lua-filter"]
828+ (ReqArg
829+ (\arg opt -> return opt { optLuaFilters = arg : optLuaFilters opt })
830+ "SCRIPTPATH")
831+ "" -- "Lua filter"
832+
817 , Option "p" ["preserve-tabs"]
833 , Option "p" ["preserve-tabs"]
818 (NoArg
834 (NoArg
819 (\opt -> return opt { optPreserveTabs = True }))
835 (\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:
8packages:
8packages:
9- '.'
9- '.'
10extra-deps:
10extra-deps:
11+- hslua-aeson-0.1.0.2
11- skylighting-0.3.1
12- skylighting-0.3.1
12- texmath-0.9.3
13- texmath-0.9.3
13resolver: lts-8.4
14resolver: 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
5import GHC.IO.Encoding
5import GHC.IO.Encoding
6import Test.Tasty
6import Test.Tasty
7import qualified Tests.Command
7import qualified Tests.Command
8+import qualified Tests.Lua
8import qualified Tests.Old
9import qualified Tests.Old
9import qualified Tests.Readers.Docx
10import qualified Tests.Readers.Docx
10import qualified Tests.Readers.EPUB
11import qualified Tests.Readers.EPUB
@@ -61,6 +62,7 @@ tests = testGroup "pandoc tests" [ Tests.Command.tests
61 , testGroup "Txt2Tags" Tests.Readers.Txt2Tags.tests
62 , testGroup "Txt2Tags" Tests.Readers.Txt2Tags.tests
62 , testGroup "EPUB" Tests.Readers.EPUB.tests
63 , testGroup "EPUB" Tests.Readers.EPUB.tests
63 ]
64 ]
65+ , testGroup "Lua filters" Tests.Lua.tests
64 ]
66 ]
65
67
66main :: IO ()
68main :: IO ()