Skip to content

Commit 8900b0f

Browse files
tarlebjgm
authored andcommitted
Lua: Support built-in default templates for custom writers
Custom writers can define a default template via a global `Template` function; the data directory is no longer searched for a default template. Writer authors can restore the old lookup behavior with ``` lua Template = function () local template return template.compile(template.default(PANDOC_SCRIPT_FILE)) end ```
1 parent 06ba4e9 commit 8900b0f

File tree

9 files changed

+104
-37
lines changed

9 files changed

+104
-37
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
test/fb2/reader/* -text
22
pandoc-lua-engine/test/*.custom -text
3+
pandoc-lua-engine/test/*.txt -text

doc/custom-writers.md

+16
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ function Writer (doc, opts)
8383
end
8484
```
8585

86+
## Default template
87+
88+
The default template of a custom writer is defined by the return
89+
value of the global function `Template`. Pandoc uses the default
90+
template for rendering when the user has not specified a template,
91+
but invoked with the `-s`/`--standalone` flag.
92+
93+
The `Template` global can be left undefined, in which case pandoc
94+
will throw an error when it would otherwise use the default
95+
template.
96+
8697
## Example: modified Markdown writer
8798

8899
Writers have access to all modules described in the [Lua filters
@@ -106,6 +117,11 @@ function Writer (doc, opts)
106117
}
107118
return pandoc.write(doc:walk(filter), 'gfm', opts)
108119
end
120+
121+
function Template ()
122+
local template = pandoc.template
123+
return template.compile(template.default 'gfm')
124+
end
109125
```
110126

111127
[Lua filters documentation]: https://pandoc.org/lua-filters.html

pandoc-lua-engine/pandoc-lua-engine.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ extra-source-files: README.md
3232
, test/tables.native
3333
, test/testsuite.native
3434
, test/writer.custom
35+
, test/writer-template.lua
36+
, test/writer-template.out.txt
3537

3638
source-repository head
3739
type: git

pandoc-lua-engine/src/Text/Pandoc/Lua/Writer.hs

+24-7
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@ import Control.Exception
2121
import Control.Monad ((<=<))
2222
import Data.Default (def)
2323
import Data.Maybe (fromMaybe)
24+
import Data.Text (Text)
2425
import HsLua
2526
import HsLua.Core.Run (newGCManagedState, withGCManagedState)
2627
import Control.Monad.IO.Class (MonadIO)
2728
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback)
28-
import Text.Pandoc.Error (PandocError)
29+
import Text.Pandoc.Error (PandocError (..))
2930
import Text.Pandoc.Format (ExtensionsConfig (..))
3031
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
3132
import Text.Pandoc.Lua.Init (runLuaWith)
3233
import Text.Pandoc.Lua.Marshal.Format (peekExtensionsConfig)
34+
import Text.Pandoc.Lua.Marshal.Template (peekTemplate)
35+
import Text.Pandoc.Templates (Template)
3336
import Text.Pandoc.Writers (Writer (..))
3437
import qualified Text.Pandoc.Lua.Writer.Classic as Classic
3538

3639
-- | Convert Pandoc to custom markup.
3740
writeCustom :: (PandocMonad m, MonadIO m)
38-
=> FilePath -> m (Writer m, ExtensionsConfig)
41+
=> FilePath -> m (Writer m, ExtensionsConfig, m (Template Text))
3942
writeCustom luaFile = do
4043
luaState <- liftIO newGCManagedState
4144
luaFile' <- fromMaybe luaFile <$> findFileWithDataFallback "writers" luaFile
@@ -56,25 +59,39 @@ writeCustom luaFile = do
5659
pushName x
5760
rawget (nth 2) <* remove (nth 2) -- remove global table
5861

59-
let writerField = "PANDOC Writer function"
62+
let writerField = "Pandoc Writer function"
6063

6164
extsConf <- rawgetglobal "writer_extensions" >>= \case
62-
TypeNil -> pure $ ExtensionsConfig mempty mempty
65+
TypeNil -> ExtensionsConfig mempty mempty <$ pop 1
6366
_ -> forcePeek $ peekExtensionsConfig top `lastly` pop 1
6467

68+
-- Store template function in registry
69+
let templateField = "Pandoc Writer Template"
70+
rawgetglobal "Template" *> setfield registryindex templateField
71+
72+
let getTemplate = liftIO $ withGCManagedState @PandocError luaState $ do
73+
getfield registryindex templateField >>= \case
74+
TypeNil -> failLua $ "No default template for writer; " <>
75+
"the global variable Template is undefined."
76+
_ -> do
77+
callTrace 0 1
78+
forcePeek $ peekTemplate top `lastly` pop 1
79+
80+
let addProperties = (, extsConf, getTemplate)
81+
6582
rawgetglobal "Writer" >>= \case
6683
TypeNil -> rawgetglobal "ByteStringWriter" >>= \case
6784
TypeNil -> do
6885
-- Neither `Writer` nor `BinaryWriter` are defined. Try to
6986
-- use the file as a classic writer.
7087
pop 1 -- remove nil
71-
pure $ (,extsConf) . TextWriter $ \opts doc ->
88+
pure $ addProperties . TextWriter $ \opts doc ->
7289
liftIO $ withGCManagedState luaState $ do
7390
Classic.runCustom @PandocError opts doc
7491
_ -> do
7592
-- Binary writer. Writer function is on top of the stack.
7693
setfield registryindex writerField
77-
pure $ (,extsConf) . ByteStringWriter $ \opts doc ->
94+
pure $ addProperties . ByteStringWriter $ \opts doc ->
7895
-- Call writer with document and writer options as arguments.
7996
liftIO $ withGCManagedState luaState $ do
8097
getfield registryindex writerField
@@ -85,7 +102,7 @@ writeCustom luaFile = do
85102
_ -> do
86103
-- New-type text writer. Writer function is on top of the stack.
87104
setfield registryindex writerField
88-
pure $ (,extsConf) . TextWriter $ \opts doc ->
105+
pure $ addProperties . TextWriter $ \opts doc ->
89106
liftIO $ withGCManagedState luaState $ do
90107
getfield registryindex writerField
91108
push doc

pandoc-lua-engine/test/Tests/Lua/Writer.hs

+17-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ tests =
3535
source <- UTF8.toText <$> readFileStrict "testsuite.native"
3636
doc <- readNative def source
3737
txt <- writeCustom "sample.lua" >>= \case
38-
(TextWriter f, _) -> f def doc
38+
(TextWriter f, _, _) -> f def doc
3939
_ -> error "Expected a text writer"
4040
pure $ BL.fromStrict (UTF8.fromText txt))
4141

@@ -45,23 +45,34 @@ tests =
4545
source <- UTF8.toText <$> readFileStrict "tables.native"
4646
doc <- readNative def source
4747
txt <- writeCustom "sample.lua" >>= \case
48-
(TextWriter f, _) -> f def doc
48+
(TextWriter f, _, _) -> f def doc
4949
_ -> error "Expected a text writer"
5050
pure $ BL.fromStrict (UTF8.fromText txt))
5151

52-
, goldenVsString "tables testsuite"
52+
, goldenVsString "bytestring writer"
5353
"bytestring.bin"
5454
(runIOorExplode $ do
5555
txt <- writeCustom "bytestring.lua" >>= \case
56-
(ByteStringWriter f, _) -> f def mempty
56+
(ByteStringWriter f, _, _) -> f def mempty
5757
_ -> error "Expected a bytestring writer"
5858
pure txt)
5959

60+
, goldenVsString "template"
61+
"writer-template.out.txt"
62+
(runIOorExplode $ do
63+
txt <- writeCustom "writer-template.lua" >>= \case
64+
(TextWriter f, _, mt) -> do
65+
template <- mt
66+
let opts = def{ writerTemplate = Just template }
67+
f opts (B.doc (B.plain (B.str "body goes here")))
68+
_ -> error "Expected a text writer"
69+
pure $ BL.fromStrict (UTF8.fromText txt))
70+
6071
, testCase "preset extensions" $ do
6172
let ediff = ExtensionsDiff{extsToEnable = [], extsToDisable = []}
6273
let format = FlavoredFormat "extensions.lua" ediff
6374
result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case
64-
(TextWriter write, extsConf) -> do
75+
(TextWriter write, extsConf, _) -> do
6576
exts <- applyExtensionsDiff extsConf format
6677
write def{writerExtensions = exts} (B.doc mempty)
6778
_ -> error "Expected a text writer"
@@ -73,7 +84,7 @@ tests =
7384
}
7485
let format = FlavoredFormat "extensions.lua" ediff
7586
result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case
76-
(TextWriter write, extsConf) -> do
87+
(TextWriter write, extsConf, _) -> do
7788
exts <- applyExtensionsDiff extsConf format
7889
write def{writerExtensions = exts} (B.doc mempty)
7990
_ -> error "Expected a text writer"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function Writer (doc, opts)
2+
return pandoc.write(doc, 'gfm', opts)
3+
end
4+
5+
function Template ()
6+
return pandoc.template.compile '<!-- start -->\n$body$\n<!-- stop -->\n'
7+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!-- start -->
2+
body goes here
3+
4+
<!-- stop -->

src/Text/Pandoc/App/OutputSettings.hs

+29-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE LambdaCase #-}
34
{-# LANGUAGE OverloadedStrings #-}
45
{-# LANGUAGE ScopedTypeVariables #-}
56
{-# LANGUAGE TupleSections #-}
@@ -104,20 +105,40 @@ optToOutputSettings scriptingEngine opts = do
104105

105106
flvrd@(Format.FlavoredFormat format _extsDiff) <-
106107
Format.parseFlavoredFormat writerName
107-
(writer, writerExts) <-
108+
109+
let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
110+
let processCustomTemplate getDefault =
111+
case optTemplate opts of
112+
_ | not standalone -> return Nothing
113+
Nothing -> Just <$> getDefault
114+
Just tp -> do
115+
-- strip off extensions
116+
let tp' = case takeExtension tp of
117+
"" -> tp <.> T.unpack format
118+
_ -> tp
119+
getTemplate tp'
120+
>>= runWithPartials . compileTemplate tp'
121+
>>= (\case
122+
Left e -> throwError $ PandocTemplateError (T.pack e)
123+
Right t -> return $ Just t)
124+
125+
(writer, writerExts, mtemplate) <-
108126
if "lua" `T.isSuffixOf` format
109127
then do
110-
(w, extsConf) <- engineWriteCustom scriptingEngine (T.unpack format)
111-
wexts <- Format.applyExtensionsDiff extsConf flvrd
112-
return (w, wexts)
128+
(w, extsConf, mt) <- engineWriteCustom scriptingEngine (T.unpack format)
129+
wexts <- Format.applyExtensionsDiff extsConf flvrd
130+
templ <- processCustomTemplate mt
131+
return (w, wexts, templ)
113132
else do
133+
tmpl <- processCustomTemplate (compileDefaultTemplate format)
114134
if optSandbox opts
115135
then case runPure (getWriter flvrd) of
116-
Right (w, wexts) -> return (makeSandboxed w, wexts)
136+
Right (w, wexts) -> return (makeSandboxed w, wexts, tmpl)
117137
Left e -> throwError e
118-
else getWriter flvrd
138+
else do
139+
(w, wexts) <- getWriter flvrd
140+
return (w, wexts, tmpl)
119141

120-
let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
121142

122143
let addSyntaxMap existingmap f = do
123144
res <- liftIO (parseSyntaxDefinition f)
@@ -186,23 +207,8 @@ optToOutputSettings scriptingEngine opts = do
186207
setVariableM "dzslides-core" dzcore vars
187208
else return vars)
188209

189-
templ <- case optTemplate opts of
190-
_ | not standalone -> return Nothing
191-
Nothing ->
192-
let filename = T.pack . takeFileName . T.unpack
193-
in Just <$> compileDefaultTemplate (filename format)
194-
Just tp -> do
195-
-- strip off extensions
196-
let tp' = case takeExtension tp of
197-
"" -> tp <.> T.unpack format
198-
_ -> tp
199-
res <- getTemplate tp' >>= runWithPartials . compileTemplate tp'
200-
case res of
201-
Left e -> throwError $ PandocTemplateError $ T.pack e
202-
Right t -> return $ Just t
203-
204210
let writerOpts = def {
205-
writerTemplate = templ
211+
writerTemplate = mtemplate
206212
, writerVariables = variables
207213
, writerTabStop = optTabStop opts
208214
, writerTableOfContents = optTableOfContents opts

src/Text/Pandoc/Scripting.hs

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Text.Pandoc.Definition (Pandoc)
2323
import Text.Pandoc.Error (PandocError (PandocNoScriptingEngine))
2424
import Text.Pandoc.Filter.Environment (Environment)
2525
import Text.Pandoc.Format (ExtensionsConfig)
26+
import Text.Pandoc.Templates (Template)
2627
import Text.Pandoc.Readers (Reader)
2728
import Text.Pandoc.Writers (Writer)
2829

@@ -40,10 +41,12 @@ data ScriptingEngine = ScriptingEngine
4041
-- ^ Function to parse input into a 'Pandoc' document.
4142

4243
, engineWriteCustom :: forall m. (PandocMonad m, MonadIO m)
43-
=> FilePath -> m (Writer m, ExtensionsConfig)
44+
=> FilePath -> m (WriterProperties m)
4445
-- ^ Invoke the given script file to convert to any custom format.
4546
}
4647

48+
type WriterProperties m = (Writer m, ExtensionsConfig, m (Template Text))
49+
4750
noEngine :: ScriptingEngine
4851
noEngine = ScriptingEngine
4952
{ engineName = "none"

0 commit comments

Comments
 (0)