From 1f8638fb5410c1ec1710a610a178376143cab311 Mon Sep 17 00:00:00 2001 From: Albert Krewinkel Date: Tue, 4 Jan 2022 10:38:02 +0100 Subject: [PATCH] Lua: add `pandoc.template` module The module provides a `compile` function to use strings as templates. --- doc/lua-filters.md | 55 +++++++++++++++++++++- pandoc.cabal | 1 + src/Text/Pandoc/Lua/Init.hs | 1 + src/Text/Pandoc/Lua/Module/Pandoc.hs | 2 +- src/Text/Pandoc/Lua/Module/Template.hs | 61 ++++++++++++++++++++++++ src/Text/Pandoc/Lua/Packages.hs | 2 + test/Tests/Lua/Module.hs | 2 + test/lua/module/pandoc-template.lua | 65 ++++++++++++++++++++++++++ test/lua/module/partial.test | 0 9 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/Text/Pandoc/Lua/Module/Template.hs create mode 100644 test/lua/module/pandoc-template.lua create mode 100644 test/lua/module/partial.test diff --git a/doc/lua-filters.md b/doc/lua-filters.md index c50fe95bf..3ca5e8a94 100644 --- a/doc/lua-filters.md +++ b/doc/lua-filters.md @@ -2121,7 +2121,7 @@ Fields: : Include table of contents (boolean) `template` -: Template to use (pandoc Template|nil) +: Template to use ([Template](#type-template)|nil) `toc_depth` : Number of levels to include in TOC (integer) @@ -2231,6 +2231,10 @@ Fields: : table rows ([List] of rows, where a row is a list of simple cells, i.e., [List] of [Blocks][]) +## Template {#type-template} + +Opaque type holding a compiled template. + ## Version {#type-version} A version object. This represents a software version like @@ -4393,6 +4397,55 @@ Returns: - The result(s) of the call to `callback` +# Module pandoc.template + +Handle pandoc templates. + +### compile {#pandoc.template.compile} + +`compile (template[, templates_path])` + +Compiles a template string into a [Template](#type-template) +object usable by pandoc. + +If the `templates_path` parameter is specified, should be the +file path associated with the template. It is used when checking +for partials. Partials will be taken only from the default data +files if this parameter is omitted. + +An error is raised if compilation fails. + +Parameters: + +`template`: +: template string (string) + +`templates_path`: +: parameter to determine a default path and extension for + partials; uses the data files templates path by default. + (string) + +Returns: + +- compiled template (Template) + +### default {#pandoc.template.default} + +`default ([writer])` + +Returns the default template for a given writer as a string. An +error if no such template can be found. + +Parameters: + +`writer`: +: name of the writer for which the template should be + retrieved; defaults to the global `FORMAT`. + +Returns: + +- raw template (string) + # Module pandoc.types Constructors for types which are not part of the pandoc AST. diff --git a/pandoc.cabal b/pandoc.cabal index e1d8ebaa7..a4a13ce0d 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -711,6 +711,7 @@ library Text.Pandoc.Lua.Module.MediaBag, Text.Pandoc.Lua.Module.Pandoc, Text.Pandoc.Lua.Module.System, + Text.Pandoc.Lua.Module.Template, Text.Pandoc.Lua.Module.Types, Text.Pandoc.Lua.Module.Utils, Text.Pandoc.Lua.Orphans, diff --git a/src/Text/Pandoc/Lua/Init.hs b/src/Text/Pandoc/Lua/Init.hs index 9e126f214..c3d792a37 100644 --- a/src/Text/Pandoc/Lua/Init.hs +++ b/src/Text/Pandoc/Lua/Init.hs @@ -48,6 +48,7 @@ loadedModules = , ("pandoc.mediabag", "mediabag") , ("pandoc.path", "path") , ("pandoc.system", "system") + , ("pandoc.template", "template") , ("pandoc.types", "types") , ("pandoc.utils", "utils") , ("text", "text") diff --git a/src/Text/Pandoc/Lua/Module/Pandoc.hs b/src/Text/Pandoc/Lua/Module/Pandoc.hs index c0127cdfe..9864da0db 100644 --- a/src/Text/Pandoc/Lua/Module/Pandoc.hs +++ b/src/Text/Pandoc/Lua/Module/Pandoc.hs @@ -29,6 +29,7 @@ import HsLua hiding (pushModule) import HsLua.Class.Peekable (PeekError) import System.Exit (ExitCode (..)) import Text.Pandoc.Definition +import Text.Pandoc.Error (PandocError (..)) import Text.Pandoc.Lua.Orphans () import Text.Pandoc.Lua.Marshal.AST import Text.Pandoc.Lua.Marshal.Filter (peekFilter) @@ -50,7 +51,6 @@ import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text as T import qualified Text.Pandoc.Lua.Util as LuaUtil import qualified Text.Pandoc.UTF8 as UTF8 -import Text.Pandoc.Error -- | Push the "pandoc" package to the Lua stack. Requires the `List` -- module to be loadable. diff --git a/src/Text/Pandoc/Lua/Module/Template.hs b/src/Text/Pandoc/Lua/Module/Template.hs new file mode 100644 index 000000000..cd66ce1c1 --- /dev/null +++ b/src/Text/Pandoc/Lua/Module/Template.hs @@ -0,0 +1,61 @@ +{-# LANGUAGE OverloadedStrings #-} +{- | + Module : Text.Pandoc.Lua.Module.Template + Copyright : Copyright © 2022 Albert Krewinkel, John MacFarlane + License : GNU GPL, version 2 or above + Maintainer : Albert Krewinkel + +Lua module to handle pandoc templates. +-} +module Text.Pandoc.Lua.Module.Template + ( documentedModule + ) where + +import HsLua +import Text.Pandoc.Error (PandocError) +import Text.Pandoc.Lua.Marshal.Template (pushTemplate) +import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua), liftPandocLua) +import Text.Pandoc.Templates + (compileTemplate, getDefaultTemplate, runWithPartials, runWithDefaultPartials) + +import qualified Data.Text as T + +-- | The "pandoc.template" module. +documentedModule :: Module PandocError +documentedModule = Module + { moduleName = "pandoc.template" + , moduleDescription = T.unlines + [ "Lua functions for pandoc templates." + ] + , moduleFields = [] + , moduleOperations = [] + , moduleFunctions = functions + } + +-- | Template module functions. +functions :: [DocumentedFunction PandocError] +functions = + [ defun "compile" + ### (\template mfilepath -> unPandocLua $ + case mfilepath of + Just fp -> runWithPartials (compileTemplate fp template) + Nothing -> runWithDefaultPartials + (compileTemplate "templates/default" template)) + <#> parameter peekText "string" "template" "template string" + <#> optionalParameter peekString "string" "templ_path" "template path" + =#> functionResult (either failLua pushTemplate) "pandoc Template" + "compiled template" + + , defun "default" + ### (\mformat -> unPandocLua $ do + let getFORMAT = liftPandocLua $ do + getglobal "FORMAT" + forcePeek $ peekText top `lastly` pop 1 + format <- maybe getFORMAT pure mformat + getDefaultTemplate format) + <#> optionalParameter peekText "string" "writer" + "writer for which the template should be returned." + =#> functionResult pushText "string" + "string representation of the writer's default template" + + ] diff --git a/src/Text/Pandoc/Lua/Packages.hs b/src/Text/Pandoc/Lua/Packages.hs index 3e4a6d650..e2f0e59a5 100644 --- a/src/Text/Pandoc/Lua/Packages.hs +++ b/src/Text/Pandoc/Lua/Packages.hs @@ -26,6 +26,7 @@ import qualified HsLua.Module.Text as Text import qualified Text.Pandoc.Lua.Module.Pandoc as Pandoc import qualified Text.Pandoc.Lua.Module.MediaBag as MediaBag import qualified Text.Pandoc.Lua.Module.System as System +import qualified Text.Pandoc.Lua.Module.Template as Template import qualified Text.Pandoc.Lua.Module.Types as Types import qualified Text.Pandoc.Lua.Module.Utils as Utils @@ -50,6 +51,7 @@ pandocPackageSearcher pkgName = "pandoc.mediabag" -> pushModuleLoader MediaBag.documentedModule "pandoc.path" -> pushModuleLoader Path.documentedModule "pandoc.system" -> pushModuleLoader System.documentedModule + "pandoc.template" -> pushModuleLoader Template.documentedModule "pandoc.types" -> pushModuleLoader Types.documentedModule "pandoc.utils" -> pushModuleLoader Utils.documentedModule "text" -> pushModuleLoader Text.documentedModule diff --git a/test/Tests/Lua/Module.hs b/test/Tests/Lua/Module.hs index 2f1a54e5c..fd3fc8998 100644 --- a/test/Tests/Lua/Module.hs +++ b/test/Tests/Lua/Module.hs @@ -27,6 +27,8 @@ tests = ("lua" "module" "pandoc-mediabag.lua") , testPandocLua "pandoc.path" ("lua" "module" "pandoc-path.lua") + , testPandocLua "pandoc.template" + ("lua" "module" "pandoc-template.lua") , testPandocLua "pandoc.types" ("lua" "module" "pandoc-types.lua") , testPandocLua "pandoc.utils" diff --git a/test/lua/module/pandoc-template.lua b/test/lua/module/pandoc-template.lua new file mode 100644 index 000000000..c288b2016 --- /dev/null +++ b/test/lua/module/pandoc-template.lua @@ -0,0 +1,65 @@ +local tasty = require 'tasty' +local template = require 'pandoc.template' + +local assert = tasty.assert +local test = tasty.test_case +local group = tasty.test_group + +return { + test('is table', function () + assert.are_equal(type(template), 'table') + end), + group 'default' { + test('is function', function () + assert.are_equal(type(template.default), 'function') + end), + test('returns a string for known format', function () + assert.are_equal( + pandoc.utils.type(template.default 'json'), + 'string' + ) + assert.are_equal( + pandoc.utils.type(template.default 'markdown'), + 'string' + ) + end), + test('fails on unknown format', function () + local success, msg = pcall(function () + return pandoc.utils.type(template.default 'nosuchformat') + end) + assert.is_falsy(success) + end), + }, + group 'compile' { + test('is function', function () + assert.are_equal(type(template.compile), 'function') + end), + test('returns a Template', function () + assert.are_equal( + pandoc.utils.type(template.compile('$title$')), + 'pandoc Template' + ) + end), + test('returns a Template', function () + local templ_path = pandoc.path.join{'lua', 'module', 'default.test'} + assert.are_equal( + pandoc.utils.type(template.compile('${ partial() }', templ_path)), + 'pandoc Template' + ) + end), + test('fails if template has non-existing partial', function () + assert.error_matches( + function () return template.compile('${ nosuchpartial() }') end, + 'PandocCouldNotFindDataFileError' + ) + end), + test('works with default template that uses partials', function () + local jats_template = template.default 'jats' + assert.are_equal(type(jats_template), 'string') + assert.are_equal( + pandoc.utils.type(template.compile(jats_template)), + 'pandoc Template' + ) + end), + }, +} diff --git a/test/lua/module/partial.test b/test/lua/module/partial.test new file mode 100644 index 000000000..e69de29bb