@ tags for quotes in HTML" (pushBool, writerHtmlQTags) (peekBool, \opts x -> opts{ writerHtmlQTags = x }) , property "identifier_prefix" "Prefix for section & note ids in HTML and for footnote marks in markdown" (pushText, writerIdentifierPrefix) (peekText, \opts x -> opts{ writerIdentifierPrefix = x }) , property "incremental" "True if lists should be incremental" (pushBool, writerIncremental) (peekBool, \opts x -> opts{ writerIncremental = x }) , property "list_of_figures" "Include list of figures" (pushBool, writerListOfFigures) (peekBool, \opts x -> opts{ writerListOfFigures = x }) , property "list_of_tables" "Include list of tables" (pushBool, writerListOfTables) (peekBool, \opts x -> opts{ writerListOfTables = x }) , property "number_offset" "Starting number for section, subsection, ..." (pushPandocList pushIntegral, writerNumberOffset) (peekList peekIntegral, \opts x -> opts{ writerNumberOffset = x }) , property "number_sections" "Number sections in LaTeX" (pushBool, writerNumberSections) (peekBool, \opts x -> opts{ writerNumberSections = x }) , property "prefer_ascii" "Prefer ASCII representations of characters when possible" (pushBool, writerPreferAscii) (peekBool, \opts x -> opts{ writerPreferAscii = x }) , property "reference_doc" "Path to reference document if specified" (maybe pushnil pushString, writerReferenceDoc) (optional . peekString, \opts x -> opts{ writerReferenceDoc = x }) , property "reference_links" "Use reference links in writing markdown, rst" (pushBool, writerReferenceLinks) (peekBool, \opts x -> opts{ writerReferenceLinks = x }) , property "reference_location" "Location of footnotes and references for writing markdown" (pushViaJSON, writerReferenceLocation) (peekViaJSON, \opts x -> opts{ writerReferenceLocation = x }) , property "figure_caption_position" "Location of caption relative to the figure" (pushViaJSON, writerFigureCaptionPosition) (peekViaJSON, \opts x -> opts{ writerFigureCaptionPosition = x }) , property "table_caption_position" "Location of caption relative to the table" (pushViaJSON, writerTableCaptionPosition) (peekViaJSON, \opts x -> opts{ writerTableCaptionPosition = x }) , property "section_divs" "Put sections in div tags in HTML" (pushBool, writerSectionDivs) (peekBool, \opts x -> opts{ writerSectionDivs = x }) , property "setext_headers" "Use setext headers for levels 1-2 in markdown" (pushBool, writerSetextHeaders) (peekBool, \opts x -> opts{ writerSetextHeaders = x }) , property "list_tables" "Render tables using list tables in RST output" (pushBool, writerListTables) (peekBool, \opts x -> opts{ writerListTables = x }) , property "slide_level" "Force header level of slides" (maybe pushnil pushIntegral, writerSlideLevel) (optional . peekIntegral, \opts x -> opts{ writerSlideLevel = x }) -- , property "syntax_map" "Syntax highlighting definition" -- (pushViaJSON, writerSyntaxMap) -- (peekViaJSON, \opts x -> opts{ writerSyntaxMap = x }) -- :: SyntaxMap , property "tab_stop" "Tabstop for conversion btw spaces and tabs" (pushIntegral, writerTabStop) (peekIntegral, \opts x -> opts{ writerTabStop = x }) , property "table_of_contents" "Include table of contents" (pushBool, writerTableOfContents) (peekBool, \opts x -> opts{ writerTableOfContents = x }) , property "template" "Template to use" (maybe pushnil pushTemplate, writerTemplate) (optional . peekTemplate, \opts x -> opts{ writerTemplate = x }) -- :: Maybe (Template Text) , property "toc_depth" "Number of levels to include in TOC" (pushIntegral, writerTOCDepth) (peekIntegral, \opts x -> opts{ writerTOCDepth = x }) , property "top_level_division" "Type of top-level divisions" (pushViaJSON, writerTopLevelDivision) (peekViaJSON, \opts x -> opts{ writerTopLevelDivision = x }) , property "variables" "Variables to set in template" (pushContext, writerVariables) (peekContext, \opts x -> opts{ writerVariables = x }) , property "wrap_text" "Option for wrapping text" (pushViaJSON, writerWrapText) (peekViaJSON, \opts x -> opts{ writerWrapText = x }) ] -- | Retrieves a 'WriterOptions' object from a table on the stack, using -- the default values for all missing fields. -- -- Internally, this pushes the default writer options, sets each -- key/value pair of the table in the userdata value, then retrieves the -- object again. This will update all fields and complain about unknown -- keys. peekWriterOptionsTable :: Peeker PandocError WriterOptions peekWriterOptionsTable idx = retrieving "WriterOptions (table)" $ do liftLua $ do absidx <- absindex idx pushUD typeWriterOptions def let setFields = do next absidx >>= \case False -> return () -- all fields were copied True -> do pushvalue (nth 2) *> insert (nth 2) settable (nth 4) -- set in userdata object setFields pushnil -- first key setFields peekUD typeWriterOptions top `lastly` pop 1 pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module.hs 0000644 0000000 0000000 00000013565 07346545000 020427 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module Copyright : © 2017-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert KrewinkelSetting up and initializing Lua modules. -} module Text.Pandoc.Lua.Module ( initModules ) where import Control.Monad (forM_, when) import Data.Version (makeVersion) import HsLua as Lua import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Marshal.List (pushPandocList, pushListModule) import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua) import qualified Data.ByteString.Char8 as Char8 import qualified Lua.LPeg as LPeg import qualified HsLua.Aeson import qualified HsLua.Module.DocLayout as Module.Layout import qualified HsLua.Module.Zip as Module.Zip import qualified Text.Pandoc.Lua.Module.CLI as Pandoc.CLI import qualified Text.Pandoc.Lua.Module.Format as Pandoc.Format import qualified Text.Pandoc.Lua.Module.Image as Pandoc.Image import qualified Text.Pandoc.Lua.Module.JSON as Pandoc.JSON import qualified Text.Pandoc.Lua.Module.Log as Pandoc.Log import qualified Text.Pandoc.Lua.Module.MediaBag as Pandoc.MediaBag import qualified Text.Pandoc.Lua.Module.Pandoc as Module.Pandoc import qualified Text.Pandoc.Lua.Module.Path as Pandoc.Path import qualified Text.Pandoc.Lua.Module.Scaffolding as Pandoc.Scaffolding import qualified Text.Pandoc.Lua.Module.Structure as Pandoc.Structure import qualified Text.Pandoc.Lua.Module.System as Pandoc.System import qualified Text.Pandoc.Lua.Module.Template as Pandoc.Template import qualified Text.Pandoc.Lua.Module.Text as Pandoc.Text import qualified Text.Pandoc.Lua.Module.Types as Pandoc.Types import qualified Text.Pandoc.Lua.Module.Utils as Pandoc.Utils initModules :: PandocLua () initModules = do initPandocModule initJsonMetatable installLpegSearcher setGlobalModules initPandocModule :: PandocLua () initPandocModule = liftPandocLua $ do -- Push module table registerModule Module.Pandoc.documentedModule -- load modules and add them to the `pandoc` module table. forM_ submodules $ \mdl -> do registerModule mdl -- pandoc.text must be require-able as 'text' for backwards compat. when (moduleName mdl == "pandoc.text") $ do getfield registryindex loaded pushvalue (nth 2) setfield (nth 2) "text" pop 1 -- _LOADED -- Shorten name, drop everything before the first dot (if any). let fieldname (Name mdlname) = Name . maybe mdlname snd . Char8.uncons . snd $ Char8.break (== '.') mdlname Lua.setfield (nth 2) (fieldname $ moduleName mdl) -- pandoc.List is low-level and must be opened differently. requirehs "pandoc.List" (const pushListModule) setfield (nth 2) "List" -- assign module to global variable Lua.setglobal "pandoc" -- | Modules that are loaded at startup and assigned to fields in the -- pandoc module. -- -- Note that @pandoc.List@ is not included here for technical reasons; -- it must be handled separately. submodules :: [Module PandocError] submodules = [ Pandoc.CLI.documentedModule , Pandoc.Format.documentedModule , Pandoc.Image.documentedModule , Pandoc.JSON.documentedModule , Pandoc.Log.documentedModule , Pandoc.MediaBag.documentedModule , Pandoc.Path.documentedModule , Pandoc.Scaffolding.documentedModule , Pandoc.Structure.documentedModule , Pandoc.System.documentedModule , Pandoc.Template.documentedModule , Pandoc.Text.documentedModule , Pandoc.Types.documentedModule , Pandoc.Utils.documentedModule , ((Module.Layout.documentedModule { moduleName = "pandoc.layout" } `allSince` [2,18]) `functionsSince` ["bold", "italic", "underlined", "strikeout", "fg", "bg"]) [3, 4, 1] , Module.Zip.documentedModule { moduleName = "pandoc.zip" } `allSince` [3,0] ] where allSince mdl version = mdl { moduleFunctions = map (`since` makeVersion version) $ moduleFunctions mdl } functionsSince mdl fns version = mdl { moduleFunctions = map (\fn -> if (functionName fn) `elem` fns then fn `since` makeVersion version else fn) $ moduleFunctions mdl } -- | Load all global modules and set them to their global variables. setGlobalModules :: PandocLua () setGlobalModules = liftPandocLua $ do let globalModules = [ ("lpeg", LPeg.luaopen_lpeg_ptr) -- must be loaded first , ("re", LPeg.luaopen_re_ptr) -- re depends on lpeg ] forM_ globalModules $ \(pkgname, luaopen) -> do Lua.pushcfunction luaopen Lua.pcall 0 1 Nothing >>= \case OK -> do -- all good, loading succeeded -- register as loaded module so later modules can rely on this Lua.getfield Lua.registryindex Lua.loaded Lua.pushvalue (Lua.nth 2) Lua.setfield (Lua.nth 2) pkgname Lua.pop 1 -- pop _LOADED _ -> do -- built-in library failed, load system lib Lua.pop 1 -- ignore error message -- Try loading via the normal package loading mechanism. Lua.getglobal "require" Lua.pushName pkgname Lua.call 1 1 -- Throws an exception if loading failed again! -- Module on top of stack. Register as global Lua.setglobal pkgname installLpegSearcher :: PandocLua () installLpegSearcher = liftPandocLua $ do Lua.getglobal' "package.searchers" Lua.pushHaskellFunction $ Lua.state >>= liftIO . LPeg.lpeg_searcher Lua.rawseti (Lua.nth 2) . (+1) . fromIntegral =<< Lua.rawlen (Lua.nth 2) Lua.pop 1 -- remove 'package.searchers' from stack -- | Setup the metatable that's assigned to Lua tables that were created -- from/via JSON arrays. initJsonMetatable :: PandocLua () initJsonMetatable = liftPandocLua $ do pushPandocList (const pushnil) [] getmetatable top setfield registryindex HsLua.Aeson.jsonarray Lua.pop 1 pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/ 0000755 0000000 0000000 00000000000 07346545000 020061 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/CLI.hs 0000644 0000000 0000000 00000010146 07346545000 021026 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.CLI Copyright : © 2022-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Command line helpers -} module Text.Pandoc.Lua.Module.CLI ( documentedModule ) where import Control.Applicative ((<|>)) import Data.Version (makeVersion) import HsLua import Text.Pandoc.App (defaultOpts, options, parseOptionsFromArgs) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.PandocLua () import qualified Data.Text as T #ifdef INCLUDE_REPL import HsLua.REPL (defaultConfig, replWithEnv, setup) #endif -- | Push the pandoc.types module on the Lua stack. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.cli" `withDescription` "Command line options and argument parsing." `withFields` [ deffield "default_options" `withType` "table" `withDescription` "Default CLI options, using a JSON-like representation." `withValue` pushViaJSON defaultOpts ] `withFunctions` [ defun "parse_options" ### parseOptions <#> parameter peekArgs "{string,...}" "args" "list of command line arguments" =#> functionResult pushViaJSON "table" "parsed options, using their JSON-like representation." #? T.unlines [ "Parses command line arguments into pandoc options." , "Typically this function will be used in stand-alone pandoc Lua" , "scripts, taking the list of arguments from the global `arg`." ] `since` makeVersion [3, 0] #ifdef INCLUDE_REPL , repl `since` makeVersion [3, 1, 2] #endif ] where peekArgs idx = (,) <$> (liftLua (rawgeti idx 0) *> (peekString top <|> pure "") `lastly` pop 1) <*> peekList peekString idx parseOptions (prg, args) = liftIO (parseOptionsFromArgs options defaultOpts prg args) >>= \case Left e -> failLua $ "Cannot process info option: " ++ show e Right opts -> pure opts #ifdef INCLUDE_REPL -- | Starts a REPL. repl :: DocumentedFunction PandocError repl = defun "repl" ### (\menvIdx -> do let repl' = case menvIdx of Nothing -> replWithEnv Nothing Just envIdx -> do settop envIdx fillWithGlobals envIdx replWithEnv . Just =<< ref registryindex setup defaultConfig repl') <#> opt (parameter (typeChecked "table" istable pure) "table" "env" ("Extra environment; the global environment is merged into this" <> " table.")) =?> T.unlines [ "The result(s) of the last evaluated input, or nothing if the last" , "input resulted in an error." ] #? T.unlines [ "Starts a read-eval-print loop (REPL). The function returns all" , "values of the last evaluated input. Exit the REPL by pressing" , "`ctrl-d` or `ctrl-c`; press `F1` to get a list of all key" , "bindings." , "" , "The REPL is started in the global namespace, unless the `env`" , "parameter is specified. In that case, the global namespace is" , "merged into the given table and the result is used as `_ENV` value" , "for the repl." , "" , "Specifically, local variables *cannot* be accessed, unless they" , "are explicitly passed via the `env` parameter; e.g." , "" , " function Pandoc (doc)" , " -- start repl, allow to access the `doc` parameter" , " -- in the repl" , " return pandoc.cli.repl{ doc = doc }" , " end" , "" , "**Note**: it seems that the function exits immediately on Windows," , "without prompting for user input." ] where fillWithGlobals idx = do -- Copy all global values into the table pushglobaltable pushnil let copyval = next (nth 2) >>= \case False -> return () True -> do pushvalue (nth 2) insert (nth 2) rawset idx copyval copyval pop 1 -- global table #endif pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Format.hs 0000644 0000000 0000000 00000006254 07346545000 021654 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.Format Copyright : © 2022-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Lua module to handle pandoc templates. -} module Text.Pandoc.Lua.Module.Format ( documentedModule ) where import Data.Version (makeVersion) import HsLua import Text.Pandoc.Error (PandocError) import Text.Pandoc.Extensions (getAllExtensions, getDefaultExtensions) import Text.Pandoc.Format (formatFromFilePaths, formatName, getExtensionsConfig) import Text.Pandoc.Lua.Marshal.Format (pushExtensions, pushExtensionsConfig) import Text.Pandoc.Lua.PandocLua () import qualified Data.Text as T -- | The "pandoc.format" module. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.format" `withDescription` T.unlines [ "Information about the formats supported by pandoc." ] `withFunctions` functions -- | Extension module functions. functions :: [DocumentedFunction PandocError] functions = [ defun "all_extensions" ### liftPure getAllExtensions <#> parameter peekText "string" "format" "format name" =#> functionResult pushExtensions "FormatExtensions" "all extensions supported for `format`" #? T.unlines [ "Returns the list of all valid extensions for a format." , "No distinction is made between input and output; an extension" , "can have an effect when reading a format but not when" , "writing it, or *vice versa*." ] `since` makeVersion [3,0] , defun "default_extensions" ### liftPure getDefaultExtensions <#> parameter peekText "string" "format" "format name" =#> functionResult pushExtensions "FormatExtensions" "default extensions enabled for `format`" #? T.unlines [ "Returns the list of default extensions of the given format; this" , "function does not check if the format is supported, it will return" , "a fallback list of extensions even for unknown formats." ] `since` makeVersion [3,0] , defun "extensions" ### liftPure getExtensionsConfig <#> textParam "format" "format identifier" =#> functionResult pushExtensionsConfig "table" "extensions config" #? T.unlines [ "Returns the extension configuration for the given format." , "The configuration is represented as a table with all supported" , "extensions as keys and their default status as value, with" , "`true` indicating that the extension is enabled by default," , "while `false` marks a supported extension that's disabled." , "" , "This function can be used to assign a value to the `Extensions`" , "global in custom readers and writers." ] `since` makeVersion [3,0] , defun "from_path" ### liftPure formatFromFilePaths <#> parameter (choice [ fmap (:[]) . peekString, peekList peekString]) "string|{string,...}" "path" "file path, or list of paths" =#> functionResult (maybe pushnil (pushText . formatName)) "string|nil" "format determined by heuristic" `since` makeVersion [3,1,2] ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Image.hs 0000644 0000000 0000000 00000005360 07346545000 021443 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-| Module : Text.Pandoc.Lua.Module.Image Copyright : © 2024 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Lua module for basic image operations. -} module Text.Pandoc.Lua.Module.Image ( -- * Module documentedModule -- ** Functions , size , format ) where import Prelude hiding (null) import Data.Default (Default (def)) import Data.Maybe (fromMaybe) import Data.Version (makeVersion) import HsLua.Core import HsLua.Marshalling import HsLua.Packaging import Text.Pandoc.Error (PandocError) import Text.Pandoc.ImageSize (imageType, imageSize) import Text.Pandoc.Lua.PandocLua () import Text.Pandoc.Lua.Marshal.ImageSize (pushImageType, pushImageSize) import Text.Pandoc.Lua.Marshal.WriterOptions (peekWriterOptions) import qualified Data.Text as T -- | The @pandoc.image@ module specification. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.image" `withDescription` "Basic image querying functions." `withFunctions` functions -- -- Functions -- functions :: [DocumentedFunction PandocError] functions = [ size `since` makeVersion [3, 1, 13] , format `since` makeVersion [3, 1, 13] ] -- | Find the size of an image. size :: DocumentedFunction PandocError size = defun "size" ### liftPure2 (\img mwriterOpts -> imageSize (fromMaybe def mwriterOpts) img) <#> parameter peekByteString "string" "image" "image data" <#> opt (parameter peekWriterOptions "WriterOptions|table" "opts" "writer options") =#> functionResult (either (failLua . T.unpack) pushImageSize) "table" "image size information or error message" #? T.unlines [ "Returns a table containing the size and resolution of an image;" , "throws an error if the given string is not an image, or if the size" , "of the image cannot be determined." , "" , "The resulting table has four entries: *width*, *height*, *dpi\\_horz*," , "and *dpi\\_vert*." , "" , "The `opts` parameter, when given, should be either a WriterOptions" , "object such as `PANDOC_WRITER_OPTIONS`, or a table with a `dpi` entry." , "It affects the calculation for vector image formats such as SVG." ] -- | Returns the format of an image. format :: LuaError e => DocumentedFunction e format = defun "format" ### liftPure imageType <#> parameter peekByteString "string" "image" "binary image data" =#> functionResult (maybe pushnil pushImageType) "string|nil" "image format, or nil if the format cannot be determined" #? T.unlines [ "Returns the format of an image as a lowercase string." , "" , "Formats recognized by pandoc include *png*, *gif*, *tiff*, *jpeg*," , "*pdf*, *svg*, *eps*, and *emf*." ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/JSON.hs 0000644 0000000 0000000 00000007150 07346545000 021171 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Text.Pandoc.Lua.Module.JSON Copyright : © 2022-2024 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Lua module to work with JSON. -} module Text.Pandoc.Lua.Module.JSON ( -- * Module documentedModule -- ** Functions , decode , encode ) where import Prelude hiding (null) import Data.Maybe (fromMaybe) import Data.Monoid (Alt (..)) import Data.Version (makeVersion) import HsLua.Aeson import HsLua.Core import HsLua.Marshalling import HsLua.Packaging import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.PandocLua () import Text.Pandoc.Lua.Marshal.AST import qualified Data.Aeson as Aeson import qualified Data.Text as T -- | The @aeson@ module specification. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.json" `withDescription` "JSON module to work with JSON; based on the Aeson Haskell package." `withFields` fields `withFunctions` functions -- -- Fields -- -- | Exported fields. fields :: LuaError e => [Field e] fields = [ null ] -- | The value used to represent the JSON @null@. null :: LuaError e => Field e null = deffield "null" `withType` "light userdata" `withDescription` "Value used to represent the `null` JSON value." `withValue` pushValue Aeson.Null -- -- Functions -- functions :: [DocumentedFunction PandocError] functions = [ decode `since` makeVersion [3, 1, 1] , encode `since` makeVersion [3, 1, 1] ] -- | Decode a JSON string into a Lua object. decode :: DocumentedFunction PandocError decode = defun "decode" ### (\str usePandocTypes -> fromMaybe pushnil . getAlt . mconcat . map Alt $ (if usePandocTypes == Just False then [] else [ pushInline <$> Aeson.decode str , pushBlock <$> Aeson.decode str , pushPandoc <$> Aeson.decode str , pushInlines <$> Aeson.decode str , pushBlocks <$> Aeson.decode str ]) ++ [pushValue <$> Aeson.decode str]) <#> parameter peekLazyByteString "string" "str" "JSON string" <#> opt (parameter peekBool "boolean" "pandoc_types" "whether to use pandoc types when possible.") =#> functionResult pure "any" "decoded object" #? T.unlines [ "Creates a Lua object from a JSON string. If the input can be decoded" , "as representing an [[Inline]], [[Block]], [[Pandoc]], [[Inlines]]," , "or [[Blocks]] element the function will return an object of the" , "appropriate type. Otherwise, if the input does not represent any" , "of the AST types, the default decoding is applied: Objects and" , "arrays are represented as tables, the JSON `null` value becomes" , "[null](#pandoc.json.null), and JSON booleans, strings, and numbers" , "are converted using the Lua types of the same name." , "" , "The special handling of AST elements can be disabled by setting" , "`pandoc_types` to `false`." ] -- | Encode a Lua object as JSON. encode :: LuaError e => DocumentedFunction e encode = defun "encode" ### liftPure Aeson.encode <#> parameter peekValue "any" "object" "object to convert" =#> functionResult pushLazyByteString "string" "JSON encoding of the given `object`" #? T.unlines ["Encodes a Lua object as JSON string." , "" , "If the object has a metamethod with name `__tojson`, then the" , "result is that of a call to that method with `object` passed as" , "the sole argument. The result of that call is expected to be a" , "valid JSON string, but this is not checked." ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Log.hs 0000644 0000000 0000000 00000007027 07346545000 021144 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Module.Log Copyright : © 2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Logging module. -} module Text.Pandoc.Lua.Module.Log ( documentedModule ) where import Data.Version (makeVersion) import HsLua import Text.Pandoc.Class (report, runSilently) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Logging (LogMessage (ScriptingInfo, ScriptingWarning)) import Text.Pandoc.Lua.Marshal.List (pushPandocList) import Text.Pandoc.Lua.Marshal.LogMessage (pushLogMessage) import Text.Pandoc.Lua.PandocLua (liftPandocLua, unPandocLua) import Text.Pandoc.Lua.SourcePos (luaSourcePos) import qualified Data.Text as T import qualified HsLua.Core.Utf8 as UTF8 -- | Push the pandoc.log module on the Lua stack. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.log" `withDescription` "Access to pandoc's logging system." `withFields` [] `withFunctions` [ defun "info" ### (\msg -> do -- reporting levels: -- 0: this function, -- 1: userdata wrapper function for the function, -- 2: function calling warn. pos <- luaSourcePos 2 unPandocLua $ report $ ScriptingInfo (UTF8.toText msg) pos) <#> parameter peekByteString "string" "message" "the info message" =#> [] #? "Reports a ScriptingInfo message to pandoc's logging system." `since` makeVersion [3, 2] , defun "silence" ### const silence <#> parameter pure "function" "fn" "function to be silenced" =?> ("List of log messages triggered during the function call, " <> "and any value returned by the function.") #? T.unlines [ "Applies the function to the given arguments while" , "preventing log messages from being added to the log." , "The warnings and info messages reported during the function" , "call are returned as the first return value, with the" , "results of the function call following thereafter." ] `since` makeVersion [3, 2] , defun "warn" ### (\msg -> do -- reporting levels: -- 0: this function, -- 1: userdata wrapper function for the function, -- 2: function calling warn. pos <- luaSourcePos 2 unPandocLua $ report $ ScriptingWarning (UTF8.toText msg) pos) <#> parameter peekByteString "string" "message" "the warning message" =#> [] #? T.unlines [ "Reports a ScriptingWarning to pandoc's logging system." , "The warning will be printed to stderr unless logging" , "verbosity has been set to *ERROR*." ] `since` makeVersion [3, 2] ] -- | Calls the function given as the first argument, but suppresses logging. -- Returns the list of generated log messages as the first result, and the other -- results of the function call after that. silence :: LuaE PandocError NumResults silence = unPandocLua $ do -- call function given as the first argument ((), messages) <- runSilently . liftPandocLua $ do nargs <- (NumArgs . subtract 1 . fromStackIndex) <$> gettop call @PandocError nargs multret liftPandocLua $ do pushPandocList pushLogMessage messages insert 1 (NumResults . fromStackIndex) <$> gettop pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/MediaBag.hs 0000644 0000000 0000000 00000025572 07346545000 022061 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.MediaBag Copyright : Copyright © 2017-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel The Lua module @pandoc.mediabag@. -} module Text.Pandoc.Lua.Module.MediaBag ( documentedModule ) where import Prelude hiding (lookup) import Data.Maybe (fromMaybe) import Data.Version (makeVersion) import HsLua ( LuaE, DocumentedFunction, Module (..) , (<#>), (###), (=#>), (=?>), (#?), defun, functionResult , opt, parameter, since, stringParam, textParam) import Text.Pandoc.Class ( fetchItem, fillMediaBag, getMediaBag, setMediaBag ) import Text.Pandoc.Class.IO (writeMedia) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc, pushPandoc) import Text.Pandoc.Lua.Marshal.List (pushPandocList) import Text.Pandoc.Lua.Orphans () import Text.Pandoc.Lua.PandocLua (unPandocLua) import Text.Pandoc.MIME (MimeType) import Text.Pandoc.SelfContained (makeDataURI) import qualified Data.ByteString.Lazy as BL import qualified Data.Text as T import qualified HsLua as Lua import qualified Text.Pandoc.MediaBag as MB -- -- MediaBag submodule -- documentedModule :: Module PandocError documentedModule = Lua.defmodule "pandoc.mediabag" `Lua.withDescription` T.unlines [ "The `pandoc.mediabag` module allows accessing pandoc's media" , "storage. The \"media bag\" is used when pandoc is called with the" , "`--extract-media` or (for HTML only) `--embed-resources` option." , "" , "The module is loaded as part of module `pandoc` and can either" , "be accessed via the `pandoc.mediabag` field, or explicitly" , "required, e.g.:" , "" , " local mb = require 'pandoc.mediabag'" ] `Lua.withFunctions` [ delete `since` makeVersion [2,7,3] , empty `since` makeVersion [2,7,3] , fetch `since` makeVersion [2,0] , fill `since` makeVersion [2,19] , insert `since` makeVersion [2,0] , items `since` makeVersion [2,7,3] , list `since` makeVersion [2,0] , lookup `since` makeVersion [2,0] , make_data_uri `since` makeVersion [3,7,1] , write `since` makeVersion [3,0] ] -- | Delete a single item from the media bag. delete :: DocumentedFunction PandocError delete = defun "delete" ### (\fp -> unPandocLua $ do mb <- getMediaBag setMediaBag $ MB.deleteMedia fp mb) <#> stringParam "filepath" ("Filename of the item to deleted. The media bag will be " <> "left unchanged if no entry with the given filename exists.") =#> [] #? "Removes a single entry from the media bag." -- | Delete all items from the media bag. empty :: DocumentedFunction PandocError empty = defun "empty" ### unPandocLua (setMediaBag mempty) =#> [] #? "Clear-out the media bag, deleting all items." -- | Fill the mediabag with all images in the document that aren't -- present yet. fill :: DocumentedFunction PandocError fill = defun "fill" ### unPandocLua . fillMediaBag <#> parameter peekPandoc "Pandoc" "doc" "document from which to fill the mediabag" =#> functionResult pushPandoc "Pandoc" "modified document" #? ("Fills the mediabag with the images in the given document.\n" <> "An image that cannot be retrieved will be replaced with a Span\n" <> "of class \"image\" that contains the image description.\n" <> "\n" <> "Images for which the mediabag already contains an item will\n" <> "not be processed again.") -- | Insert a new item into the media bag. insert :: DocumentedFunction PandocError insert = defun "insert" ### (\fp mmime contents -> unPandocLua $ do mb <- getMediaBag setMediaBag $ MB.insertMedia fp mmime contents mb return (Lua.NumResults 0)) <#> stringParam "filepath" "filename and path relative to the output folder." <#> parameter (Lua.peekNilOr Lua.peekText) "string|nil" "mimetype" "the item's MIME type; use `nil` if the MIME type is\ \ unknown or unavailable." <#> parameter Lua.peekLazyByteString "string" "contents" "the binary contents of the file." =#> [] #? T.unlines [ "Adds a new entry to pandoc's media bag. Replaces any existing" , "media bag entry the same `filepath`." , "" , "Usage:" , "" , " local fp = 'media/hello.txt'" , " local mt = 'text/plain'" , " local contents = 'Hello, World!'" , " pandoc.mediabag.insert(fp, mt, contents)" ] -- | Returns iterator values to be used with a Lua @for@ loop. items :: DocumentedFunction PandocError items = defun "items" ### (do mb <- unPandocLua getMediaBag let pushItem (fp, mimetype, contents) = do Lua.pushString fp Lua.pushText mimetype Lua.pushByteString $ BL.toStrict contents return (Lua.NumResults 3) Lua.pushIterator pushItem (MB.mediaItems mb)) =?> T.unlines [ "Iterator triple:" , "" , "- The iterator function; must be called with the iterator" , " state and the current iterator value." , "- Iterator state -- an opaque value to be passed to the" , " iterator function." , "- Initial iterator value." ] #? T.unlines [ "Returns an iterator triple to be used with Lua's generic `for`" , "statement. The iterator returns the filepath, MIME type, and" , "content of a media bag item on each invocation. Items are" , "processed one-by-one to avoid excessive memory use." , "" , "This function should be used only when full access to all items," , "including their contents, is required. For all other cases," , "[`list`](#pandoc.mediabag.list) should be preferred." , "" , "Usage:" , "" , " for fp, mt, contents in pandoc.mediabag.items() do" , " -- print(fp, mt, contents)" , " end" ] -- | Function to lookup a value in the mediabag. lookup :: DocumentedFunction PandocError lookup = defun "lookup" ### (\fp -> unPandocLua (MB.lookupMedia fp <$> getMediaBag)) <#> stringParam "filepath" "name of the file to look up." =#> mconcat [ functionResult (maybe Lua.pushnil (Lua.pushText . MB.mediaMimeType)) "string" "The entry's MIME type, or nil if the file was not found." , functionResult (maybe Lua.pushnil (Lua.pushLazyByteString . MB.mediaContents)) "string" "Contents of the file, or nil if the file was not found." ] #? T.unlines [ "Lookup a media item in the media bag, and return its MIME type" , "and contents." , "" , "Usage:" , "" , " local filename = 'media/diagram.png'" , " local mt, contents = pandoc.mediabag.lookup(filename)" ] -- | Function listing all mediabag items. list :: DocumentedFunction PandocError list = defun "list" ### (unPandocLua (MB.mediaDirectory <$> getMediaBag)) =#> functionResult (pushPandocList pushEntry) "table" ("A list of elements summarizing each entry in the media\n" <> "bag. The summary item contains the keys `path`, `type`, and\n" <> "`length`, giving the filepath, MIME type, and length of\n" <> "contents in bytes, respectively.") #? T.unlines [ "Get a summary of the current media bag contents." , "" , "Usage:" , "" , " -- calculate the size of the media bag." , " local mb_items = pandoc.mediabag.list()" , " local sum = 0" , " for i = 1, #mb_items do" , " sum = sum + mb_items[i].length" , " end" , " print(sum)" ] where pushEntry :: (FilePath, MimeType, Int) -> LuaE PandocError () pushEntry (fp, mimeType, contentLength) = do Lua.newtable Lua.pushName "path" *> Lua.pushString fp *> Lua.rawset (-3) Lua.pushName "type" *> Lua.pushText mimeType *> Lua.rawset (-3) Lua.pushName "length" *> Lua.pushIntegral contentLength *> Lua.rawset (-3) -- | Lua function to retrieve a new item. fetch :: DocumentedFunction PandocError fetch = defun "fetch" ### (unPandocLua . fetchItem) <#> textParam "source" "path to a resource; either a local file path or URI" =#> ( functionResult (Lua.pushText . fromMaybe "" . snd) "string" "The entry's MIME type, or `nil` if the file was not found." <> functionResult (Lua.pushByteString . fst) "string" "Contents of the file, or `nil` if the file was not found." ) #? T.unlines [ "Fetches the given source from a URL or local file. Returns two" , "values: the contents of the file and the MIME type (or an empty" , "string)." , "" , "The function will first try to retrieve `source` from the" , "mediabag; if that fails, it will try to download it or read it" , "from the local file system while respecting pandoc's \"resource" , "path\" setting." , "" , "Usage:" , "" , " local diagram_url = 'https://pandoc.org/diagram.jpg'" , " local mt, contents = pandoc.mediabag.fetch(diagram_url)" ] make_data_uri :: DocumentedFunction PandocError make_data_uri = defun "make_data_uri" ### (\mime raw -> pure $ makeDataURI (mime, raw)) <#> parameter Lua.peekText "string" "mime_type" "MIME type of the data" <#> parameter Lua.peekByteString "string" "raw_data" "data to encode" =#> functionResult Lua.pushText "string" "data uri" #? T.unlines [ "Convert the input data into a data URI as defined by RFC 2397." , "" , "Example:" , "" , " -- Embed an unofficial pandoc logo" , " local pandoc_logo_url = 'https://raw.githubusercontent.com/'" , " .. 'tarleb/pandoc-logo/main/pandoc.svg'" , "" , " local datauri = pandoc.mediabag.make_data_uri(" , " pandoc.mediabag.fetch(pandoc_logo_url)" , " )" , "" , " local image = pandoc.Image('Logo', datauri)" ] -- | Extract the mediabag or just a single entry. write :: DocumentedFunction PandocError write = defun "write" ### (\dir mfp -> do mb <- unPandocLua getMediaBag case mfp of Nothing -> unPandocLua $ mapM_ (writeMedia dir) (MB.mediaItems mb) Just fp -> do case MB.lookupMedia fp mb of Nothing -> Lua.failLua ("Resource not in mediabag: " <> fp) Just item -> unPandocLua $ do let triple = ( MB.mediaPath item , MB.mediaMimeType item , MB.mediaContents item ) writeMedia dir triple) <#> stringParam "dir" "path of the target directory" <#> opt (stringParam "fp" "canonical name (relative path) of resource") =#> [] #? T.unlines [ "Writes the contents of mediabag to the given target directory. If" , "`fp` is given, then only the resource with the given name will be" , "extracted. Omitting that parameter means that the whole mediabag" , "gets extracted. An error is thrown if `fp` is given but cannot be" , "found in the mediabag." ] `since` makeVersion [3, 0] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Pandoc.hs 0000644 0000000 0000000 00000050022 07346545000 021620 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Module.Pandoc Copyright : Copyright © 2017-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Main @pandoc@ module, containing element constructors and central functions. -} module Text.Pandoc.Lua.Module.Pandoc ( documentedModule ) where import Prelude hiding (read) import Control.Applicative ((<|>)) import Control.Monad (foldM, forM_, when) import Control.Monad.Catch (catch, handle, throwM) import Control.Monad.Except (MonadError (throwError)) import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr) import Data.Default (Default (..)) import Data.Maybe (fromMaybe) import Data.Proxy (Proxy (Proxy)) import Data.Text.Encoding.Error (UnicodeException) import Data.Version (makeVersion) import HsLua import System.Exit (ExitCode (..)) import Text.Pandoc.Class ( PandocMonad, FileInfo (..), FileTree , addToFileTree, getCurrentTime , getRequestHeaders, getResourcePath, getUserDataDir , setRequestHeaders, setResourcePath, setUserDataDir , insertInFileTree, sandboxWithFileTree ) import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError (..)) import Text.Pandoc.Format (FlavoredFormat, parseFlavoredFormat) import Text.Pandoc.Lua.Orphans () import Text.Pandoc.Lua.Marshal.AST import Text.Pandoc.Lua.Marshal.Format (peekFlavoredFormat) import Text.Pandoc.Lua.Marshal.Filter (peekFilter) import Text.Pandoc.Lua.Marshal.ReaderOptions ( peekReaderOptions , pushReaderOptions) import Text.Pandoc.Lua.Marshal.Sources (peekSources) import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions , pushWriterOptions) import Text.Pandoc.Lua.Module.Utils (sha1) import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua)) import Text.Pandoc.Lua.Writer.Classic (runCustom) import Text.Pandoc.Options ( ReaderOptions (readerExtensions) , WriterOptions (writerExtensions) ) import Text.Pandoc.Process (pipeProcess) import Text.Pandoc.Readers (Reader (..), getReader, readers) import Text.Pandoc.Sources (toSources) import Text.Pandoc.Writers (Writer (..), getWriter, writers) import qualified HsLua as Lua import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text as T import qualified Text.Pandoc.UTF8 as UTF8 documentedModule :: Module PandocError documentedModule = defmodule "pandoc" `withDescription` T.unlines [ "Fields and functions for pandoc scripts; includes constructors for" , "document tree elements, functions to parse text in a given" , "format, and functions to filter and modify a subtree." ] `withFields` readersField : writersField : stringConstants ++ [inlineField, blockField] `withFunctions` mconcat [ [mkPandoc, mkMeta] , metaValueConstructors , blockConstructors , [mkBlocks] , inlineConstructors , [mkInlines] , otherConstructors , functions ] `associateType` typePandoc `associateType` typeBlock `associateType` typeInline -- | Set of input formats accepted by @read@. readersField :: Field PandocError readersField = deffield "readers" `withType` "table" `withDescription` T.unlines [ "Set of formats that pandoc can parse. All keys in this table can" , "be used as the `format` value in `pandoc.read`." ] `withValue` pushKeyValuePairs pushText (pushText . readerType) (readers @PandocLua) where readerType = \case TextReader {} -> "text" ByteStringReader {} -> "bytestring" -- | Set of input formats accepted by @write@. writersField :: Field PandocError writersField = deffield "writers" `withType` "table" `withDescription` T.unlines [ "Set of formats that pandoc can generate. All keys in this table" , "can be used as the `format` value in `pandoc.write`." ] `withValue` pushKeyValuePairs pushText (pushText . writerType) (writers @PandocLua) where writerType = \case TextWriter {} -> "text" ByteStringWriter {} -> "bytestring" -- | Inline table field inlineField :: Field PandocError inlineField = deffield "Inline" `withType` "table" `withDescription` "Inline constructors, nested under 'constructors'." -- the nesting happens for historical reasons and should probably be -- changed. `withValue` pushWithConstructorsSubtable inlineConstructors -- | @Block@ module field blockField :: Field PandocError blockField = deffield "Block" `withType` "table" `withDescription` "Inline constructors, nested under 'constructors'." -- the nesting happens for historical reasons and should probably be -- changed. `withValue` pushWithConstructorsSubtable blockConstructors pushWithConstructorsSubtable :: [DocumentedFunction PandocError] -> LuaE PandocError () pushWithConstructorsSubtable constructors = do newtable -- Field table newtable -- constructor table pushName "constructor" *> pushvalue (nth 2) *> rawset (nth 4) forM_ constructors $ \fn -> do pushName (functionName fn) pushDocumentedFunction fn rawset (nth 3) pop 1 -- pop constructor table otherConstructors :: [DocumentedFunction PandocError] otherConstructors = [ mkAttr , mkCaption `since` makeVersion [3,6,1] , mkCell , mkAttributeList , mkCitation , mkListAttributes , mkRow , mkTableFoot , mkTableHead , mkSimpleTable , defun "ReaderOptions" ### liftPure id <#> parameter peekReaderOptions "ReaderOptions|table" "opts" (T.unlines [ "Either a table with a subset of the properties of a" , "[[ReaderOptions]] object, or another ReaderOptions object." , "Uses the defaults specified in the manual for all" , "properties that are not explicitly specified. Throws an" , "error if a table contains properties which are not present" , "in a ReaderOptions object." ] ) =#> functionResult pushReaderOptions "ReaderOptions" "new object" #? T.unlines [ "Creates a new ReaderOptions value." , "" , "Usage:" , "" , " -- copy of the reader options that were defined on the command line." , " local cli_opts = pandoc.ReaderOptions(PANDOC_READER_OPTIONS)" , " -- default reader options, but columns set to 66." , " local short_colums_opts = pandoc.ReaderOptions {columns = 66}" ] , defun "WriterOptions" ### liftPure id <#> parameter peekWriterOptions "WriterOptions|table" "opts" (T.unlines [ "Either a table with a subset of the properties of a" , "[[WriterOptions]] object, or another WriterOptions object." , "Uses the defaults specified in the manual for all" , "properties that are not explicitly specified. Throws an" , "error if a table contains properties which are not present" , "in a WriterOptions object." ]) =#> functionResult pushWriterOptions "WriterOptions" "new object" #? "Creates a new WriterOptions value." ] stringConstants :: [Field e] stringConstants = let constrs :: forall a. Data a => Proxy a -> [String] constrs _ = map showConstr . dataTypeConstrs . dataTypeOf @a $ undefined nullaryConstructors = mconcat [ constrs (Proxy @ListNumberStyle) , constrs (Proxy @ListNumberDelim) , constrs (Proxy @QuoteType) , constrs (Proxy @MathType) , constrs (Proxy @Alignment) , constrs (Proxy @CitationMode) ] toField s = deffield (Name $ UTF8.fromString s) `withType` "string" `withDescription` T.pack s `withValue` pushString s in map toField nullaryConstructors functions :: [DocumentedFunction PandocError] functions = [ defun "pipe" ### (\command args input -> do (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input `catch` (throwM . PandocIOError "pipe") case ec of ExitSuccess -> 1 <$ Lua.pushLazyByteString output ExitFailure n -> do pushPipeError (PipeError (T.pack command) n output) Lua.error) <#> parameter peekString "string" "command" "path to executable" <#> parameter (peekList peekString) "{string,...}" "args" "list of arguments" <#> parameter peekLazyByteString "string" "input" "input passed to process via stdin" =?> "output string, or error triple" , defun "read" ### (\content mformatspec mreaderOptions mreadEnv -> do let readerOpts = fromMaybe def mreaderOptions readAction :: PandocMonad m => FlavoredFormat -> m Pandoc readAction flvrd = getReader flvrd >>= \case (TextReader r, es) -> r readerOpts{readerExtensions = es} $ case content of Left bs -> toSources $ UTF8.toText bs Right sources -> sources (ByteStringReader r, es) -> case content of Left bs -> r readerOpts{readerExtensions = es} (BSL.fromStrict bs) Right _ -> throwError $ PandocLuaError "Cannot use bytestring reader with Sources" handle (failLua . show @UnicodeException) . unPandocLua $ do flvrd <- maybe (parseFlavoredFormat "markdown") pure mformatspec case mreadEnv of Nothing -> readAction flvrd Just tree -> sandboxWithFileTree tree (readAction flvrd)) <#> parameter (\idx -> (Left <$> peekByteString idx) <|> (Right <$> peekSources idx)) "string|Sources" "content" "text to parse" <#> opt (parameter peekFlavoredFormat "string|table" "formatspec" "format and extensions") <#> opt (parameter peekReaderOptions "ReaderOptions" "reader_options" "reader options") <#> opt (parameter peekReadEnv "table" "read_env" $ T.unlines [ "If the value is not given or `nil`, then the global environment" , "is used. Passing a list of filenames causes the reader to" , "be run in a sandbox. The given files are read from the file" , "system and provided to the sandbox via an ersatz file system." , "The table can also contain mappings from filenames to" , "contents, which will be used to populate the ersatz file" , "system." ]) =#> functionResult pushPandoc "Pandoc" "result document" , sha1 , defun "walk_block" ### walkElement <#> parameter peekBlockFuzzy "Block" "block" "element to traverse" <#> parameter peekFilter "Filter" "lua_filter" "filter functions" =#> functionResult pushBlock "Block" "modified Block" , defun "walk_inline" ### walkElement <#> parameter peekInlineFuzzy "Inline" "inline" "element to traverse" <#> parameter peekFilter "Filter" "lua_filter" "filter functions" =#> functionResult pushInline "Inline" "modified Inline" , defun "with_state" ### with_state <#> parameter peekStateOptions "table" "options" "state options" <#> parameter pure "function" "callback" "The action to run with the given state." =?> "The results of the call to *callback*." #? "Runs a function with a modified pandoc state.\n\ \\n\ \The given callback is invoked after setting the pandoc state to the\ \ given values. The modifiable options are restored to their original\ \ values once the callback has returned.\n\ \\n\ \The following state variables can be controlled:\n\ \\n\ \ - `request_headers` (list of key-value tuples)\n\ \ - `resource_path` (list of filepaths)\n\ \ - `user_data_dir` (string)\n\ \\n\ \Other options are ignored, and the rest of the state is not modified.\n\ \\n\ \Usage:\n\ \\n\ \ local opts = {\n\ \ request_headers = {\n\ \ {'Authorization', 'Basic my-secret'}\n\ \ }\n\ \ }\n\ \ pandoc.with_state(opts, function ()\n\ \ local mime, contents = pandoc.mediabag.fetch(image_url)\n\ \ )\n" , defun "write" ### (\doc mformatspec mwriterOpts -> unPandocLua $ do flvrd <- maybe (parseFlavoredFormat "markdown") pure mformatspec let writerOpts = fromMaybe def mwriterOpts getWriter flvrd >>= \case (TextWriter w, es) -> Right <$> w writerOpts{ writerExtensions = es } doc (ByteStringWriter w, es) -> Left <$> w writerOpts{ writerExtensions = es } doc) <#> parameter peekPandoc "Pandoc" "doc" "document to convert" <#> opt (parameter peekFlavoredFormat "string|table" "formatspec" (T.unlines [ "format specification; defaults to `\"html\"`. See the" , "documentation of [`pandoc.read`](#pandoc.read) for a complete" , "description of this parameter." ])) <#> opt (parameter peekWriterOptions "WriterOptions|table" "writer_options" (T.unlines [ "options passed to the writer; may be a WriterOptions object" , "or a table with a subset of the keys and values of a" , "WriterOptions object; defaults to the default values" , "documented in the manual." ]) ) =#> functionResult (either pushLazyByteString pushText) "string" "result document" #? T.unlines [ "Converts a document to the given target format." , "" , "Usage:" , "" , " local doc = pandoc.Pandoc(" , " {pandoc.Para {pandoc.Strong 'Tea'}}" , " )" , " local html = pandoc.write(doc, 'html')" , " assert(html == ' Tea
')" ] , defun "write_classic" ### (\doc mwopts -> runCustom (fromMaybe def mwopts) doc) <#> parameter peekPandoc "Pandoc" "doc" "document to convert" <#> opt (parameter peekWriterOptions "WriterOptions" "writer_options" (T.unlines [ "options passed to the writer; may be a WriterOptions object" , "or a table with a subset of the keys and values of a" , "WriterOptions object; defaults to the default values" , "documented in the manual." ])) =#> functionResult pushText "string" "rendered document" #? (T.unlines [ "Runs a classic custom Lua writer, using the functions defined" , "in the current environment." , "" , "Usage:" , "" , " -- Adding this function converts a classic writer into a" , " -- new-style custom writer." , " function Writer (doc, opts)" , " PANDOC_DOCUMENT = doc" , " PANDOC_WRITER_OPTIONS = opts" , " loadfile(PANDOC_SCRIPT_FILE)()" , " return pandoc.write_classic(doc, opts)" , " end" ]) ] where walkElement x f = walkInlineSplicing f x >>= walkInlinesStraight f >>= walkBlockSplicing f >>= walkBlocksStraight f data PipeError = PipeError { pipeErrorCommand :: T.Text , pipeErrorCode :: Int , pipeErrorOutput :: BL.ByteString } peekPipeError :: LuaError e => StackIndex -> LuaE e PipeError peekPipeError idx = PipeError <$> (Lua.getfield idx "command" *> Lua.peek (-1) <* Lua.pop 1) <*> (Lua.getfield idx "error_code" *> Lua.peek (-1) <* Lua.pop 1) <*> (Lua.getfield idx "output" *> Lua.peek (-1) <* Lua.pop 1) pushPipeError :: LuaError e => Pusher e PipeError pushPipeError pipeErr = do pushAsTable [ ("command" , pushText . pipeErrorCommand) , ("error_code" , pushIntegral . pipeErrorCode) , ("output" , pushLazyByteString . pipeErrorOutput) ] pipeErr pushPipeErrorMetaTable Lua.setmetatable (nth 2) where pushPipeErrorMetaTable :: LuaError e => LuaE e () pushPipeErrorMetaTable = do v <- Lua.newmetatable "pandoc pipe error" when v $ do pushName "__tostring" pushHaskellFunction pipeErrorMessage rawset (nth 3) pipeErrorMessage :: LuaError e => LuaE e NumResults pipeErrorMessage = do (PipeError cmd errorCode output) <- peekPipeError (nthBottom 1) pushByteString . BSL.toStrict . BSL.concat $ [ BSL.pack "Error running " , BSL.pack $ T.unpack cmd , BSL.pack " (error code " , BSL.pack $ show errorCode , BSL.pack "): " , if output == mempty then BSL.pack "" else output ] return (NumResults 1) -- | Peek the environment in which the `read` function operates. peekReadEnv :: Peeker PandocError FileTree peekReadEnv idx = do mtime <- liftLua . unPandocLua $ getCurrentTime -- Add files from file system files <- peekList peekString idx tree1 <- liftLua $ foldM (\tree fp -> liftIO $ addToFileTree tree fp) mempty files -- Add files from key-value pairs let toFileInfo contents = FileInfo { infoFileMTime = mtime , infoFileContents = contents } pairs <- peekKeyValuePairs peekString (fmap toFileInfo . peekByteString) idx let tree2 = foldr (uncurry insertInFileTree) tree1 pairs -- Return ersatz file system. pure tree2 -- | Helper type that holds all common state values that can be controlled. -- -- This is closely related to "CommonState", but that's an opaque value -- that can only be read and modified through accessor functions. All -- fields in this type can be modified through accessors. data StateOptions = StateOptions { stateOptsRequestHeaders :: [(T.Text, T.Text)] , stateOptsResourcePath :: [String] , stateOptsUserDataDir :: Maybe String } -- | Peek pandoc state options; the current state properties are used for -- unspecified values. peekStateOptions :: Peeker PandocError StateOptions peekStateOptions idx = do absidx <- liftLua $ absindex idx let setOptions opts = do liftLua (next absidx) >>= \case False -> return opts True -> do key <- peekByteString (nth 2) case key of "request_headers" -> do let peekHeaderPair = peekPair peekText peekText value <- peekList peekHeaderPair top `lastly` pop 1 setOptions $ opts { stateOptsRequestHeaders = value } "resource_path" -> do value <- peekList peekString top `lastly` pop 1 setOptions $ opts { stateOptsResourcePath = value } "user_data_dir" -> do value <- peekNilOr peekString top `lastly` pop 1 setOptions $ opts { stateOptsUserDataDir = value } _ -> do liftLua $ pop 2 -- remove key and value failPeek $ "Unknown or unsupported state option: " <> key liftLua pushnil -- first "key" liftLua getStateOptions >>= setOptions -- | Get the current options values from the pandoc state. getStateOptions :: LuaE PandocError StateOptions getStateOptions = unPandocLua $ StateOptions <$> getRequestHeaders <*> getResourcePath <*> getUserDataDir -- | Update the pandoc state with the new options. setStateOptions :: StateOptions -> LuaE PandocError () setStateOptions opts = unPandocLua $ do setRequestHeaders $ stateOptsRequestHeaders opts setResourcePath $ stateOptsResourcePath opts setUserDataDir $ stateOptsUserDataDir opts -- | Run a callback with a modified pandoc state. with_state :: StateOptions -> StackIndex -> LuaE PandocError NumResults with_state options callback_idx = do origState <- getStateOptions setStateOptions options -- Invoke the callback oldTop <- gettop pushvalue callback_idx call 0 multret newTop <- gettop setStateOptions origState return . NumResults . fromStackIndex $ newTop - oldTop pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Path.hs 0000644 0000000 0000000 00000003031 07346545000 021306 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Module.Path Copyright : © 2019-2026 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Pandoc's system Lua module. -} module Text.Pandoc.Lua.Module.Path ( documentedModule ) where import Data.Version (makeVersion) import HsLua import qualified HsLua.Module.Path as MPath import qualified HsLua.Module.System as MSystem -- | Push the pandoc.system module on the Lua stack. documentedModule :: forall e. LuaError e => Module e documentedModule = defmodule "pandoc.path" `withDescription` moduleDescription @e MPath.documentedModule `withFields` [ MPath.separator , MPath.search_path_separator ] `withFunctions` [ MPath.directory `since` v[2,12] , MSystem.exists `since` v[3,7,1] , MPath.filename `since` v[2,12] , MPath.is_absolute `since` v[2,12] , MPath.is_relative `since` v[2,12] , MPath.join `since` v[2,12] , MPath.make_relative `since` v[2,12] , MPath.normalize `since` v[2,12] , MPath.split `since` v[2,12] , MPath.split_extension `since` v[2,12] , MPath.split_search_path `since` v[2,12] , MPath.treat_strings_as_paths `since` v[2,12] ] where v = makeVersion pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Scaffolding.hs 0000644 0000000 0000000 00000003071 07346545000 022635 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.Scaffolding Copyright : Copyright © 2022-2026 Albert Krewinkel, John MacFarlane License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Scaffolding for custom Writers. -} module Text.Pandoc.Lua.Module.Scaffolding ( documentedModule ) where import HsLua import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Writer.Scaffolding (pushWriterScaffolding) import qualified Data.Text as T -- | The "pandoc.template" module. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.scaffolding" `withDescription` "Scaffolding for custom writers." `withFields` [writerScaffolding] -- | Template module functions. writerScaffolding :: Field PandocError writerScaffolding = deffield "Writer" `withType` "table" `withDescription` T.unlines [ "An object to be used as a `Writer` function; the construct handles" , "most of the boilerplate, expecting only render functions for all" , "AST elements" ] `withValue` do pushWriterScaffolding -- pretend that it's a submodule so we get better error messages getfield registryindex loaded pushvalue (nth 2) setfield (nth 2) (submod "Writer") -- same for fields "Block" and "Inline" getfield (nth 2) "Inline" *> setfield (nth 2) (submod "Writer.Inline") getfield (nth 2) "Block" *> setfield (nth 2) (submod "Writer.Block") pop 1 -- remove "LOADED_TABLE" where submod x = moduleName documentedModule <> "." <> x pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Structure.hs 0000644 0000000 0000000 00000023727 07346545000 022430 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.Structure Copyright : © 2023-2026 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Command line helpers -} module Text.Pandoc.Lua.Module.Structure ( documentedModule ) where import Control.Applicative ((<|>), optional) import Data.Default (Default (..)) import Data.Maybe (fromMaybe) import Data.Version (makeVersion) import HsLua ( DocumentedFunction, LuaError, Module (..), Peeker , (###), (<#>), (=#>), (#?), defmodule , defun, functionResult, getfield, isnil, lastly, liftLua , opt, liftPure, parameter , peekBool, peekIntegral , peekFieldRaw, peekSet, peekText, pop, pushIntegral , pushText, since, top, withDescription, withFunctions ) import Text.Pandoc.Chunks ( ChunkedDoc (..), PathTemplate (..) , tocToList, splitIntoChunks ) import Text.Pandoc.Definition (Pandoc (..), Block) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.PandocLua () import Text.Pandoc.Lua.Marshal.AST ( peekBlocksFuzzy, peekInlinesFuzzy , peekPandoc, pushBlock, pushBlocks ) import Text.Pandoc.Lua.Marshal.Chunks import Text.Pandoc.Lua.Marshal.Format (peekExtensions) import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions ) import Text.Pandoc.Options (WriterOptions (writerTOCDepth, writerNumberSections)) import Text.Pandoc.Slides (getSlideLevel, prepSlides) import Text.Pandoc.Writers.Shared (toTableOfContents) import qualified Data.Text as T import qualified Text.Pandoc.Shared as Shared -- | Push the pandoc.structure module on the Lua stack. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.structure" `withDescription` "Access to the higher-level document structure, including " <> "hierarchical sections and the table of contents." `withFunctions` [ make_sections `since` makeVersion [3,0] , slide_level `since` makeVersion [3,0] , split_into_chunks `since` makeVersion [3,0] , table_of_contents `since` makeVersion [3,0] , unique_identifier `since` makeVersion [3,8] ] make_sections :: LuaError e => DocumentedFunction e make_sections = defun "make_sections" ### (\blks mopts -> let (numSects, baseLevel, mslideLevel) = fromMaybe (defNumSec, Nothing, Nothing) mopts blks' = case mslideLevel of Just l | l <= 0 -> prepSlides (getSlideLevel blks) blks Just sl -> prepSlides sl blks Nothing -> blks in pure $ Shared.makeSections numSects baseLevel blks') <#> parameter peekBodyBlocks "Blocks|Pandoc" "blocks" "document blocks to process" <#> opt (parameter peekOpts "table" "opts" "options") =#> functionResult pushBlocks "Blocks" "processed blocks" #? T.unlines [ "Puts [[Blocks]] into a hierarchical structure: a list of sections" , "(each a Div with class \"section\" and first element a Header)." , "" , "The optional `opts` argument can be a table; two settings are" , "recognized: If `number_sections` is true, a `number` attribute" , "containing the section number will be added to each `Header`. If" , "`base_level` is an integer, then `Header` levels will be" , "reorganized so that there are no gaps, with numbering levels" , "shifted by the given value. Finally, an integer `slide_level`" , "value triggers the creation of slides at that heading level." , "" , "Note that a [[WriterOptions]] object can be passed as the opts" , "table; this will set the `number_section` and `slide_level` values" , "to those defined on the command line." , "" , "Usage:" , "" , " local blocks = {" , " pandoc.Header(2, pandoc.Str 'first')," , " pandoc.Header(2, pandoc.Str 'second')," , " }" , " local opts = PANDOC_WRITER_OPTIONS" , " local newblocks = pandoc.structure.make_sections(blocks, opts)" ] where defNumSec = False peekOpts idx = do numberSections <- fromMaybe defNumSec <$> do liftLua $ getfield idx "number_sections" optional (peekBool top `lastly` pop 1) baseLevel <- do liftLua $ getfield idx "base_level" optional (peekIntegral top `lastly` pop 1) slideLevel <- do liftLua $ getfield idx "slide_level" optional (peekIntegral top `lastly` pop 1) return (numberSections, baseLevel, slideLevel) slide_level :: LuaError e => DocumentedFunction e slide_level = defun "slide_level" ### liftPure getSlideLevel <#> parameter peekBodyBlocks "Blocks|Pandoc" "blocks" "document body" =#> functionResult pushIntegral "integer" "slide level" #? T.unlines [ "Find level of header that starts slides (defined as the least" , "header level that occurs before a non-header/non-hrule in the" , "blocks)." ] -- | Split 'Pandoc' into 'Chunk's. split_into_chunks :: LuaError e => DocumentedFunction e split_into_chunks = defun "split_into_chunks" ### (\doc mopts -> pure $ let defOpts = (defPathTmpl, defNumSects, Nothing, defLvl) (pathTempl, numberSect, mbBaseLevel, chunkLevel) = fromMaybe defOpts mopts in splitIntoChunks pathTempl numberSect mbBaseLevel chunkLevel doc) <#> parameter peekPandoc "Pandoc" "doc" "document to split" <#> opt (parameter peekSplitOpts "table" "opts" optionsDescr) =#> functionResult pushChunkedDoc "ChunkedDoc" "" #? T.unlines [ "Converts a [[Pandoc]] document into a [[ChunkedDoc]]." ] where defPathTmpl = PathTemplate "chunk-%n" defNumSects = False defLvl = 1 peekSplitOpts idx = (,,,) <$> peekFieldRaw ((fmap PathTemplate . peekText) `orDefault` defPathTmpl) "path_template" idx <*> peekFieldRaw (peekBool `orDefault` defNumSects) "number_sections" idx <*> peekFieldRaw (optional . peekIntegral) "base_heading_level" idx <*> peekFieldRaw (peekIntegral `orDefault` defLvl) "chunk_level" idx orDefault p defaultValue idx' = liftLua (isnil idx') >>= \case True -> pure defaultValue False -> p idx' optionsDescr = T.unlines [ "Splitting options." , "" , "The following options are supported:" , "" , " `path_template`" , " : template used to generate the chunks' filepaths" , " `%n` will be replaced with the chunk number (padded with" , " leading 0s to 3 digits), `%s` with the section number of" , " the heading, `%h` with the (stringified) heading text," , " `%i` with the section identifier. For example," , " `\"section-%s-%i.html\"` might be resolved to" , " `\"section-1.2-introduction.html\"`." , "" , " Default is `\"chunk-%n\"` (string)" , "" , " `number_sections`" , " : whether sections should be numbered; default is `false`" , " (boolean)" , "" , " `chunk_level`" , " : The heading level the document should be split into" , " chunks. The default is to split at the top-level, i.e.," , " `1`. (integer)" , "" , " `base_heading_level`" , " : The base level to be used for numbering. Default is `nil`" , " (integer|nil)" ] -- | Generate a table of contents. table_of_contents :: DocumentedFunction PandocError table_of_contents = defun "table_of_contents" ### (\tocSource mwriterOpts -> pure $ let writerOpts = fromMaybe def mwriterOpts in case tocSource of Left blks -> toTableOfContents writerOpts blks Right tree -> tocToList (writerNumberSections writerOpts) (writerTOCDepth writerOpts) tree ) <#> parameter peekTocSource "Blocks|Pandoc|ChunkedDoc" "toc_source" "list of command line arguments" <#> opt (parameter peekWriterOptions "WriterOptions" "opts" "options") =#> functionResult pushBlock "Block" "Table of contents as a BulletList object" #? T.unlines [ "Generates a table of contents for the given object." ] where peekTocSource idx = (Left <$> peekBodyBlocks idx) <|> (Right . chunkedTOC <$> peekChunkedDoc idx) -- | Generate a unique ID from a list of inlines. unique_identifier :: LuaError e => DocumentedFunction e unique_identifier = defun "unique_identifier" ### (\inlns mUsedIdents mExts -> do let usedIdents = fromMaybe mempty mUsedIdents let exts = fromMaybe mempty mExts pure $ Shared.uniqueIdent exts inlns usedIdents) <#> parameter peekInlinesFuzzy "Inlines" "inlines" "base for identifier" <#> opt (parameter (peekSet peekText) "table" "used" "set of identifiers (string keys, boolean values) that\ \ have already been used.") <#> opt (parameter peekExtensions "{string,...}" "exts" "list of format extensions") =#> functionResult pushText "string" "unique identifier" #? "Generates a unique identifier from a list of inlines, similar to\ \ what's generated by the `auto_identifiers` extension.\n\ \\n\ \ The method used to generated identifiers can be modified through\ \ `ext`, which is a list of format extensions.\n\ \\n\ \ It can be used to generate IDs similar to what the `auto_identifiers`\ \ extension provides.\n\ \\n\ \ Example:\n\ \\n\ \ local used_ids = {}\n\ \ function Header (h)\n\ \ local id =\n\ \ pandoc.structure.unique_identifier(h.content, used_ids)\n\ \ used_ids[id] = true\n\ \ h.identifier = id\n\ \ return h\n\ \ end" -- | Retrieves the body blocks of a 'Pandoc' object or from a list of -- blocks. peekBodyBlocks :: LuaError e => Peeker e [Block] peekBodyBlocks idx = ((\(Pandoc _ blks) -> blks) <$> peekPandoc idx) <|> peekBlocksFuzzy idx pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/System.hs 0000644 0000000 0000000 00000004251 07346545000 021703 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Module.System Copyright : © 2019-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Pandoc's system Lua module. -} module Text.Pandoc.Lua.Module.System ( documentedModule ) where import Data.Version (makeVersion) import HsLua import HsLua.Module.System ( arch, cmd, cp, cputime, env, getwd, ls, mkdir, os, read_file , rename, rm, rmdir, times, with_env, with_tmpdir, with_wd , write_file, xdg ) import qualified HsLua.Module.System as MSys -- | Push the pandoc.system module on the Lua stack. documentedModule :: forall e. LuaError e => Module e documentedModule = defmodule "pandoc.system" `withDescription` moduleDescription @e MSys.documentedModule `withFields` [arch, os] `withFunctions` [ cputime `since` v[3,1,1] , setName cmd "command" `since` v[3,7,1] , setName cp "copy" `since` v[3,7,1] , setName env "environment" `since` v[2,7,3] , setName getwd "get_working_directory" `since` v[2,8] , setName ls "list_directory" `since` v[2,19] , setName mkdir "make_directory" `since` v[2,19] , read_file `since` v[3,7,1] , rename `since` v[3,7,1] , setName rm "remove" `since` v[3,7,1] , setName rmdir "remove_directory" `since` v[2,19] , times `since` v[3,7,1] , setName with_env "with_environment" `since` v[2,7,3] , setName with_tmpdir "with_temporary_directory" `since` v[2,8] , setName with_wd "with_working_directory" `since` v[2,7,3] , write_file `since` v[3,7,1] , xdg `since` v[3,7,1] ] where v = makeVersion pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Template.hs 0000644 0000000 0000000 00000012577 07346545000 022204 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.Template Copyright : Copyright © 2022-2026 Albert Krewinkel, John MacFarlane License : GPL-2.0-or-later Maintainer : Albert Krewinkel Lua module to handle pandoc templates. -} module Text.Pandoc.Lua.Module.Template ( documentedModule ) where import Data.Version (makeVersion) import HsLua import HsLua.Module.DocLayout (peekDoc, pushDoc) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Marshal.AST (peekMeta, pushBlocks, pushInlines) import Text.Pandoc.Lua.Marshal.Context (peekContext, pushContext) import Text.Pandoc.Lua.Marshal.Template (typeTemplate, peekTemplate, pushTemplate) import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua), liftPandocLua) import Text.Pandoc.Writers.Shared (metaToContext') import Text.Pandoc.Templates ( compileTemplate, getDefaultTemplate, getTemplate, renderTemplate , runWithPartials, runWithDefaultPartials ) import qualified Data.Text as T -- | The "pandoc.template" module. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.template" `withDescription` "Handle pandoc templates." `withFunctions` functions `associateType` typeTemplate -- | Template module functions. functions :: [DocumentedFunction PandocError] functions = [ defun "apply" ### liftPure2 renderTemplate <#> parameter peekTemplate "Template" "template" "template to apply" <#> parameter peekContext "table" "context" "variable values" =#> functionResult pushDoc "Doc" "rendered template" #? T.unlines [ "Applies a context with variable assignments to a template," , "returning the rendered template. The `context` parameter must be a" , "table with variable names as keys and [[Doc]], string, boolean, or" , "table as values, where the table can be either be a list of the" , "aforementioned types, or a nested context." ] `since` makeVersion [3,0] , 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" <#> opt (stringParam "templates_path" ("parameter to determine a default path and extension for " <> "partials; uses the data files templates path by default.")) =#> functionResult (either failLua pushTemplate) "Template" "compiled template" #? T.unlines [ "Compiles a template string into a [[Template]] object usable by" , "pandoc." , "" , "If the `templates_path` parameter is specified, then it 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." ] `since` makeVersion [2,17] , defun "default" ### (\mformat -> unPandocLua $ do let getFORMAT = liftPandocLua $ do getglobal "FORMAT" forcePeek $ peekText top `lastly` pop 1 format <- maybe getFORMAT pure mformat getDefaultTemplate format) <#> opt (textParam "writer" ("name of the writer for which the template should be " <> "retrieved; defaults to the global `FORMAT`.")) =#> functionResult pushText "string" "raw template" #? T.unlines [ "Returns the default template for a given writer as a string. An" , "error is thrown if no such template can be found." ] `since` makeVersion [2,17] , defun "get" ### (unPandocLua . getTemplate) <#> stringParam "filename" "name of the template" =#> textResult "content of template file" #? T.unlines [ "Retrieve text for a template." , "" , "This function first checks the resource paths for a file of this" , "name; if none is found, the `templates` directory in the user data" , "directory is checked. Returns the content of the file, or throws" , "an error if no file is found." ] `since` makeVersion [3,2,1] , defun "meta_to_context" ### (\meta blockWriterIdx inlineWriterIdx -> unPandocLua $ do let blockWriter blks = liftPandocLua $ do pushvalue blockWriterIdx pushBlocks blks callTrace 1 1 forcePeek $ peekDoc top let inlineWriter blks = liftPandocLua $ do pushvalue inlineWriterIdx pushInlines blks callTrace 1 1 forcePeek $ peekDoc top metaToContext' blockWriter inlineWriter meta) <#> parameter peekMeta "Meta" "meta" "document metadata" <#> parameter pure "function" "blocks_writer" "converter from [[Blocks]] to [[Doc]] values" <#> parameter pure "function" "inlines_writer" "converter from [[Inlines]] to [[Doc]] values" =#> functionResult pushContext "table" "template context" #? T.unlines [ "Creates template context from the document's [[Meta]] data, using the" , "given functions to convert [[Blocks]] and [[Inlines]] to [[Doc]]" , "values." ] `since` makeVersion [3,0] ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Text.hs 0000644 0000000 0000000 00000005467 07346545000 021355 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-| Module : Text.Pandoc.Lua.Module.Text Copyright : © 2023 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Lua module to work with UTF-8 strings. -} module Text.Pandoc.Lua.Module.Text ( documentedModule ) where import Data.Version (makeVersion) import HsLua import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.PandocLua () import Text.Pandoc.Writers.Shared (toSubscript, toSuperscript) import qualified Data.Text as T import qualified HsLua.Module.Text as TM -- | The @aeson@ module specification. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.text" `withFunctions` [ TM.fromencoding `since` v[3,0] , TM.len `since` v[2,0,3] , TM.lower `since` v[2,0,3] , TM.reverse `since` v[2,0,3] , TM.sub `since` v[2,0,3] , subscript `since` v[3,8] , superscript `since` v[3,8] , TM.toencoding `since` v[3,0] , TM.upper `since` v[2,0,3] ] `withDescription` T.unlines [ "UTF-8 aware text manipulation functions, implemented in Haskell." , "" , "The text module can also be loaded under the name `text`, although" , "this is discouraged and deprecated." , "" , "``` lua" , "-- uppercase all regular text in a document:" , "function Str (s)" , " s.text = pandoc.text.upper(s.text)" , " return s" , "end" , "```" ] where v = makeVersion -- | Convert all chars in a string to Unicode subscript. subscript :: LuaError e => DocumentedFunction e subscript = defun "subscript" ### pure . traverse toSubscript <#> stringParam "input" "string to convert to subscript characters" =#> functionResult (maybe pushnil pushString) "string|nil" "Subscript version of the input, or `nil` if not all characters\ \ could be converted." #? "Tries to convert the string into a Unicode subscript version of the\ \ string. Returns `nil` if not all characters of the input can be\ \ mapped to a subscript Unicode character.\ \ Supported characters include numbers, parentheses, and plus/minus." -- | Convert all chars in a string to Unicode superscript. superscript :: LuaError e => DocumentedFunction e superscript = defun "superscript" ### pure . traverse toSuperscript <#> stringParam "input" "string to convert to superscript characters" =#> functionResult (maybe pushnil pushString) "string|nil" "Superscript version of the input, or `nil` if not all characters\ \ could be converted." #? "Tries to convert the string into a Unicode superscript version of the\ \ string. Returns `nil` if not all characters of the input can be\ \ mapped to a superscript Unicode character.\ \ Supported characters include numbers, parentheses, and plus/minus." pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Types.hs 0000644 0000000 0000000 00000002700 07346545000 021520 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Module.Types Copyright : © 2019-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Pandoc data type constructors. -} module Text.Pandoc.Lua.Module.Types ( documentedModule ) where import Data.Version (makeVersion) import HsLua ( Module (..), (###), (<#>), (=#>) , defmodule, defun, functionResult, parameter, since , withDescription, withFunctions ) import HsLua.Module.Version (peekVersionFuzzy, pushVersion) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.PandocLua () -- | Push the pandoc.types module on the Lua stack. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.types" `withDescription` "Constructors for types that are not part of the pandoc AST." `withFunctions` [ defun "Version" ### return <#> parameter peekVersionFuzzy "string|number|{integer,...}|Version" "version_specifier" (mconcat [ "A version string like `'2.7.3'`, " , "a Lua number like `2.0`, " , "a list of integers like `{2,7,3}`, " , "or a Version object." ]) =#> functionResult pushVersion "Version" "New Version object." `since` makeVersion [2,7,3] ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Module/Utils.hs 0000644 0000000 0000000 00000043101 07346545000 021514 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Module.Utils Copyright : © 2017-2026 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Utility module for Lua, exposing internal helper functions. -} module Text.Pandoc.Lua.Module.Utils ( documentedModule , sha1 ) where import Control.Applicative ((<|>)) import Control.Monad ((<$!>)) import Control.Monad.Except (MonadError (throwError)) import Crypto.Hash (hashWith, SHA1(SHA1)) import Data.Data (showConstr, toConstr) import Data.Default (def) import Data.Maybe (fromMaybe) import Data.Version (Version, makeVersion) import HsLua as Lua import HsLua.Module.Version (peekVersionFuzzy, pushVersion) import Text.Pandoc.Citeproc (getReferences, processCitations) import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError (PandocLuaError)) import Text.Pandoc.Filter (applyJSONFilter) import Text.Pandoc.Format (FlavoredFormat (formatName), parseFlavoredFormat) import Text.Pandoc.Lua.Documentation (renderDocumentation) import Text.Pandoc.Lua.Filter (runFilterFile') import Text.Pandoc.Lua.Marshal.AST import Text.Pandoc.Lua.Marshal.Format (peekFlavoredFormat) import Text.Pandoc.Lua.Marshal.Reference import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua)) import Text.Pandoc.Options (WriterOptions (writerExtensions)) import Text.Pandoc.Writers (Writer (..), getWriter) import qualified Data.Map as Map import qualified Data.Text as T import qualified Text.Pandoc.Builder as B import qualified Text.Pandoc.Shared as Shared import qualified Text.Pandoc.UTF8 as UTF8 import qualified Text.Pandoc.Writers.Shared as Shared -- | Push the "pandoc.utils" module to the Lua stack. documentedModule :: Module PandocError documentedModule = defmodule "pandoc.utils" `withDescription` T.unlines [ "This module exposes internal pandoc functions and utility" , "functions." ] `withFunctions` [ blocks_to_inlines `since` v[2,2,3] , citeproc `since` v[2,19,1] , documentation `since` v[3,8,4] , equals `since` v[2,5] , from_simple_table `since` v[2,11] , make_sections `since` v[2,8] , normalize_date `since` v[2,0,6] , references `since` v[2,17] , run_json_filter `since` v[2,1,1] , run_lua_filter `since` v[3,2,1] , sha1 `since` v[2,0,6] , stringify `since` v[2,0,6] , to_roman_numeral `since` v[2,0,6] , to_simple_table `since` v[2,11] , type' `since` v[2,17] , defun "Version" ### liftPure (id @Version) <#> parameter peekVersionFuzzy "Version|string|{integer,...}|number" "v" "version description" =#> functionResult pushVersion "Version" "new Version object" #? "Creates a Version object." ] where v = makeVersion blocks_to_inlines :: LuaError e => DocumentedFunction e blocks_to_inlines = defun "blocks_to_inlines" ### (\blks mSep -> do let sep = maybe Shared.defaultBlocksSeparator B.fromList mSep return $ B.toList (Shared.blocksToInlinesWithSep sep blks)) <#> parameter (peekList peekBlock) "Blocks" "blocks" "List of [[Block]] elements to be flattened." <#> opt (parameter (peekList peekInline) "Inlines" "sep" ("List of [[Inline]] elements inserted as separator between\n" <> "two consecutive blocks; defaults to `{pandoc.LineBreak()}`.")) =#> functionResult pushInlines "Inlines" "" #? T.unlines [ "Squash a list of blocks into a list of inlines." , "" , "Usage" , "" , " local blocks = {" , " pandoc.Para{ pandoc.Str 'Paragraph1' }," , " pandoc.Para{ pandoc.Emph 'Paragraph2' }" , " }" , " local inlines = pandoc.utils.blocks_to_inlines(blocks)" , " assert(" , " inlines == pandoc.Inlines {" , " pandoc.Str 'Paragraph1'," , " pandoc.Linebreak()," , " pandoc.Emph{ pandoc.Str 'Paragraph2' }" , " }" , " )" ] citeproc :: DocumentedFunction PandocError citeproc = defun "citeproc" ### unPandocLua . processCitations <#> parameter peekPandoc "Pandoc" "doc" "document" =#> functionResult pushPandoc "Pandoc" "processed document" #? T.unlines [ "Process the citations in the file, replacing them with " , "rendered citations and adding a bibliography. " , "See the manual section on citation rendering for details." , "" , "Usage:" , "" , " -- Lua filter that behaves like `--citeproc`" , " function Pandoc (doc)" , " return pandoc.utils.citeproc(doc)" , " end" ] documentation :: DocumentedFunction PandocError documentation = defun "documentation" ### (\idx mformat -> do docobj <- getdocumentation idx >>= \case TypeNil -> fail "Undocumented object" _ -> forcePeek $ peekDocumentationObject top let blocks = renderDocumentation docobj if maybe mempty formatName mformat == "blocks" then pure . Left $ B.toList blocks else unPandocLua $ do flvrd <- maybe (parseFlavoredFormat "ansi") pure mformat getWriter flvrd >>= \case (TextWriter w, es) -> Right <$> w def{ writerExtensions = es } (B.doc blocks) _ -> throwError $ PandocLuaError "ByteString writers are not supported here.") <#> parameter pure "any" "object" "Retrieve documentation for this object" <#> opt (parameter peekFlavoredFormat "string|table" "format" "result format; defaults to `'ansi'`") =#> functionResult (either pushBlocks pushText) "string|Blocks" "rendered documentation" #? "Return the documentation for a function or module defined by pandoc.\ \ Throws an error if there is no documentation for the given object.\n\ \\n\ \The result format can be any textual format accepted by `pandoc.write`,\ \ and the documentation will be returned in that format.\ \ Additionally, the special format `blocks` is accepted, in which case\ \ the documentation is returned as [[Blocks]]." equals :: LuaError e => DocumentedFunction e equals = defun "equals" ### equal <#> parameter pure "any" "element1" "" <#> parameter pure "any" "element2" "" =#> functionResult pushBool "boolean" "Whether the two objects represent the same element" #? T.unlines [ "Test equality of AST elements. Elements in Lua are considered" , "equal if and only if the objects obtained by unmarshaling are" , "equal." , "" , "**This function is deprecated.** Use the normal Lua `==` equality" , "operator instead." ] -- | Converts an old/simple table into a normal table block element. from_simple_table :: LuaError e => DocumentedFunction e from_simple_table = defun "from_simple_table" ### liftPure (\(SimpleTable capt aligns widths head' body) -> Table nullAttr (Caption Nothing [Plain capt | not (null capt)]) (zipWith (\a w -> (a, toColWidth w)) aligns widths) (TableHead nullAttr [blockListToRow head' | not (null head') ]) [TableBody nullAttr 0 [] $ map blockListToRow body | not (null body)] (TableFoot nullAttr [])) <#> parameter peekSimpleTable "SimpleTable" "simple_tbl" "" =#> functionResult pushBlock "Block" "table block element" #? T.unlines [ "Creates a [[Table]] block element from a [[SimpleTable]]. This is" , "useful for dealing with legacy code which was written for pandoc" , "versions older than 2.10." , "" , "Usage:" , "" , " local simple = pandoc.SimpleTable(table)" , " -- modify, using pre pandoc 2.10 methods" , " simple.caption = pandoc.SmallCaps(simple.caption)" , " -- create normal table block again" , " table = pandoc.utils.from_simple_table(simple)" ] where blockListToRow :: [[Block]] -> Row blockListToRow = Row nullAttr . map (B.simpleCell . B.fromList) toColWidth :: Double -> ColWidth toColWidth 0 = ColWidthDefault toColWidth w = ColWidth w make_sections :: LuaError e => DocumentedFunction e make_sections = defun "make_sections" ### liftPure3 Shared.makeSections <#> parameter peekBool "boolean" "number_sections" ("whether section divs should get an additional `number`\n" <> "attribute containing the section number.") <#> parameter (\i -> (Nothing <$ peekNil i) <|> (Just <$!> peekIntegral i)) "integer|nil" "baselevel" "shift top-level headings to this level" <#> parameter (peekList peekBlock) "Blocks" "blocks" "list of blocks to process" =#> functionResult pushBlocks "Blocks" "blocks with sections" #? T.unlines [ "Converts a list of [[Block]] elements into sections." , "`Div`s will be created beginning at each `Header`" , "and containing following content until the next `Header`" , "of comparable level. If `number_sections` is true," , "a `number` attribute will be added to each `Header`" , "containing the section number. If `base_level` is" , "non-null, `Header` levels will be reorganized so" , "that there are no gaps, and so that the base level" , "is the level specified." ] normalize_date :: DocumentedFunction e normalize_date = defun "normalize_date" ### liftPure Shared.normalizeDate <#> parameter peekText "string" "date" "the date string" =#> functionResult (maybe pushnil pushText) "string|nil" "normalized date, or nil if normalization failed." #? T.unwords [ "Parse a date and convert (if possible) to \"YYYY-MM-DD\" format. We" , "limit years to the range 1601-9999 (ISO 8601 accepts greater than" , "or equal to 1583, but MS Word only accepts dates starting 1601)." , "Returns nil instead of a string if the conversion failed." ] -- | List of references in CSL format. references :: DocumentedFunction PandocError references = defun "references" ### (unPandocLua . getReferences Nothing) <#> parameter peekPandoc "Pandoc" "doc" "document" =#> functionResult (pushPandocList pushReference) "table" "lift of references." #? T.unlines [ "Get references defined inline in the metadata and via an external" , "bibliography. Only references that are actually cited in the" , "document (either with a genuine citation or with `nocite`) are" , "returned. URL variables are converted to links." , "" , "The structure used represent reference values corresponds to that" , "used in CSL JSON; the return value can be use as `references`" , "metadata, which is one of the values used by pandoc and citeproc" , "when generating bibliographies." , "" , "Usage:" , "" , " -- Include all cited references in document" , " function Pandoc (doc)" , " doc.meta.references = pandoc.utils.references(doc)" , " doc.meta.bibliography = nil" , " return doc" , " end" ] -- | Run a filter from a file. run_lua_filter :: DocumentedFunction PandocError run_lua_filter = defun "run_lua_filter" ### (\doc fp mbenv -> do envIdx <- maybe copyOfGlobalTable pure mbenv runFilterFile' envIdx fp doc) <#> parameter peekPandoc "Pandoc" "doc" "the Pandoc document to filter" <#> parameter peekString "string" "filter" "filepath of the filter to run" <#> opt (parameter (typeChecked "table" istable pure) "table" "env" "environment to load and run the filter in") =#> functionResult pushPandoc "Pandoc" "filtered document" #? ( "Filter the given doc by passing it through a Lua filter." <> "\n\nThe filter will be run in the current Lua process." <> "\n" ) where copynext :: LuaError e => StackIndex -> LuaE e StackIndex copynext to = Lua.next (nth 2) >>= \case False -> pure to True -> do pushvalue (nth 2) insert (nth 2) rawset to copynext to copyOfGlobalTable :: LuaError e => LuaE e StackIndex copyOfGlobalTable = do newtable pushglobaltable pushnil (copynext =<< absindex (nth 3)) <* pop 1 -- pop source table -- | Process the document with a JSON filter. run_json_filter :: DocumentedFunction PandocError run_json_filter = defun "run_json_filter" ### (\doc filterPath margs -> do args <- case margs of Just xs -> return xs Nothing -> do Lua.getglobal "FORMAT" (forcePeek ((:[]) <$!> peekString top) <* pop 1) applyJSONFilter def args filterPath doc ) <#> parameter peekPandoc "Pandoc" "doc" "the Pandoc document to filter" <#> parameter peekString "string" "filter" "filter to run" <#> opt (parameter (peekList peekString) "{string,...}" "args" "list of arguments passed to the filter. Defaults to `{FORMAT}`.") =#> functionResult pushPandoc "Pandoc" "filtered document" #? "Filter the given doc by passing it through a JSON filter." -- | Documented Lua function to compute the hash of a string. sha1 :: DocumentedFunction e sha1 = defun "sha1" ### liftPure (show . hashWith SHA1) <#> parameter peekByteString "string" "input" "" =#> functionResult pushString "string" "hexadecimal hash value" #? "Computes the SHA1 hash of the given string input." -- | Convert pandoc structure to a string with formatting removed. -- Footnotes are skipped (since we don't want their contents in link -- labels). stringify :: LuaError e => DocumentedFunction e stringify = defun "stringify" ### (\idx -> forcePeek . retrieving "stringifyable element" $ choice [ (fmap Shared.stringify . peekPandoc) , (fmap Shared.stringify . peekInline) , (fmap Shared.stringify . peekBlock) , (fmap Shared.stringify . peekCaption) , (fmap Shared.stringify . peekCell) , (fmap Shared.stringify . peekCitation) , (fmap Shared.stringify . peekTableHead) , (fmap Shared.stringify . peekTableFoot) , (fmap stringifyMetaValue . peekMetaValue) , (fmap (const "") . peekAttr) , (fmap (const "") . peekListAttributes) ] idx) <#> parameter pure "Pandoc|Block|Inline|Caption|Cell|MetaValue" "element" "some pandoc AST element" =#> functionResult pushText "string" "A plain string representation of the given element." #? T.unlines [ "Converts the given element (Pandoc, Meta, Block, or Inline) into" , "a string with all formatting removed." ] where stringifyMetaValue :: MetaValue -> T.Text stringifyMetaValue mv = case mv of MetaBool b -> T.toLower $ T.pack (show b) MetaString s -> s MetaList xs -> mconcat $ map stringifyMetaValue xs MetaMap m -> mconcat $ map (stringifyMetaValue . snd) (Map.toList m) _ -> Shared.stringify mv to_roman_numeral :: LuaError e => DocumentedFunction e to_roman_numeral = defun "to_roman_numeral" ### liftPure Shared.toRomanNumeral <#> parameter (peekIntegral @Int) "integer" "n" "positive integer below 4000" =#> functionResult pushText "string" "A roman numeral." #? T.unlines [ "Converts an integer < 4000 to uppercase roman numeral." , "" , "Usage:" , "" , " local to_roman_numeral = pandoc.utils.to_roman_numeral" , " local pandoc_birth_year = to_roman_numeral(2006)" , " -- pandoc_birth_year == 'MMVI'" ] -- | Converts a table into an old/simple table. to_simple_table :: DocumentedFunction PandocError to_simple_table = defun "to_simple_table" ### (\case Table _attr caption specs thead tbodies tfoot -> do let (capt, aligns, widths, headers, rows) = Shared.toLegacyTable caption specs thead tbodies tfoot return $ SimpleTable capt aligns widths headers rows blk -> Lua.failLua $ mconcat [ "Expected Table, got ", showConstr (toConstr blk), "." ]) <#> parameter peekTable "Block" "tbl" "a table" =#> functionResult pushSimpleTable "SimpleTable" "SimpleTable object" #? T.unlines [ "Converts a table into an old/simple table." , "" , "Usage:" , "" , " local simple = pandoc.utils.to_simple_table(table)" , " -- modify, using pre pandoc 2.10 methods" , " simple.caption = pandoc.SmallCaps(simple.caption)" , " -- create normal table block again" , " table = pandoc.utils.from_simple_table(simple)" ] where peekTable :: LuaError e => Peeker e Block peekTable idx = peekBlock idx >>= \case t@(Table {}) -> return t b -> Lua.failPeek $ mconcat [ "Expected Table, got " , UTF8.fromString $ showConstr (toConstr b) , "." ] type' :: DocumentedFunction e type' = defun "type" ### (\idx -> getmetafield idx "__name" >>= \case TypeString -> fromMaybe mempty <$> tostring top _ -> ltype idx >>= typename) <#> parameter pure "any" "value" "any Lua value" =#> functionResult pushByteString "string" "type of the given value" #? T.unlines [ "Pandoc-friendly version of Lua's default `type` function, returning" , "type information similar to what is presented in the manual." , "" , "The function works by checking the metafield `__name`. If the" , "argument has a string-valued metafield `__name`, then it returns" , "that string. Otherwise it behaves just like the normal `type`" , "function." , "" , "Usage:" , "" , " -- Prints one of 'string', 'boolean', 'Inlines', 'Blocks'," , " -- 'table', and 'nil', corresponding to the Haskell constructors" , " -- MetaString, MetaBool, MetaInlines, MetaBlocks, MetaMap," , " -- and an unset value, respectively." , "" , " function Meta (meta)" , " print('type of metavalue `author`:', pandoc.utils.type(meta.author))" , " end" ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Orphans.hs 0000644 0000000 0000000 00000005136 07346545000 020607 0 ustar 00 0000000 0000000 {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE FlexibleInstances #-} {- | Module : Text.Pandoc.Lua.Orphans Copyright : © 2012-2024 John MacFarlane © 2017-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Orphan instances for Lua's Pushable and Peekable type classes. -} module Text.Pandoc.Lua.Orphans () where import Data.Version (Version) import HsLua import HsLua.Module.Version (peekVersionFuzzy) import Text.Pandoc.Definition import Text.Pandoc.Lua.Marshal.AST import Text.Pandoc.Lua.Marshal.CommonState () import Text.Pandoc.Lua.Marshal.Context () import Text.Pandoc.Lua.Marshal.PandocError() import Text.Pandoc.Lua.Marshal.ReaderOptions () import Text.Pandoc.Lua.Marshal.Sources (pushSources) import Text.Pandoc.Sources (Sources) instance Pushable Pandoc where push = pushPandoc instance Pushable Meta where push = pushMeta instance Pushable MetaValue where push = pushMetaValue instance Pushable Block where push = pushBlock instance {-# OVERLAPPING #-} Pushable [Block] where push = pushBlocks instance Pushable Alignment where push = pushString . show instance Pushable CitationMode where push = pushCitationMode instance Pushable Format where push = pushFormat instance Pushable ListNumberDelim where push = pushString . show instance Pushable ListNumberStyle where push = pushString . show instance Pushable MathType where push = pushMathType instance Pushable QuoteType where push = pushQuoteType instance Pushable Cell where push = pushCell instance Pushable Inline where push = pushInline instance {-# OVERLAPPING #-} Pushable [Inline] where push = pushInlines instance Pushable Citation where push = pushCitation instance Pushable Row where push = pushRow instance Pushable TableBody where push = pushTableBody instance Pushable TableFoot where push = pushTableFoot instance Pushable TableHead where push = pushTableHead -- These instances exist only for testing. It's a hack to avoid making -- the marshalling modules public. instance Peekable Inline where safepeek = peekInline instance Peekable Block where safepeek = peekBlock instance Peekable Cell where safepeek = peekCell instance Peekable Meta where safepeek = peekMeta instance Peekable Pandoc where safepeek = peekPandoc instance Peekable Row where safepeek = peekRow instance Peekable Version where safepeek = peekVersionFuzzy instance {-# OVERLAPPING #-} Peekable Attr where safepeek = peekAttr instance Pushable Sources where push = pushSources pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/PandocLua.hs 0000644 0000000 0000000 00000006175 07346545000 021047 0 ustar 00 0000000 0000000 {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {- | Module : Text.Pandoc.Lua.PandocLua Copyright : © 2020-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel PandocMonad instance which allows execution of Lua operations and which uses Lua to handle state. -} module Text.Pandoc.Lua.PandocLua ( PandocLua (..) , liftPandocLua ) where import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) import Control.Monad.Except (MonadError (catchError, throwError)) import Control.Monad.IO.Class (MonadIO) import HsLua as Lua import Text.Pandoc.Class (PandocMonad (..)) import Text.Pandoc.Error (PandocError (..)) import Text.Pandoc.Lua.Marshal.CommonState (peekCommonState, pushCommonState) import Text.Pandoc.Lua.Marshal.PandocError (peekPandocError, pushPandocError) import qualified Control.Monad.Catch as Catch import qualified Data.Text as T import qualified Text.Pandoc.Class.IO as IO -- | Type providing access to both, pandoc and Lua operations. newtype PandocLua a = PandocLua { unPandocLua :: LuaE PandocError a } deriving ( Applicative , Functor , Monad , MonadCatch , MonadIO , MonadMask , MonadThrow ) -- | Lift a @'Lua'@ operation into the @'PandocLua'@ type. liftPandocLua :: LuaE PandocError a -> PandocLua a liftPandocLua = PandocLua instance {-# OVERLAPPING #-} Exposable PandocError (PandocLua NumResults) where partialApply _narg = liftLua . unPandocLua instance Pushable a => Exposable PandocError (PandocLua a) where partialApply _narg x = 1 <$ (liftLua (unPandocLua x >>= Lua.push)) instance MonadError PandocError PandocLua where catchError = Catch.catch throwError = Catch.throwM instance PandocMonad PandocLua where lookupEnv = IO.lookupEnv getCurrentTime = IO.getCurrentTime getCurrentTimeZone = IO.getCurrentTimeZone newStdGen = IO.newStdGen newUniqueHash = IO.newUniqueHash openURL = IO.openURL readFileLazy = IO.readFileLazy readFileStrict = IO.readFileStrict readStdinStrict = IO.readStdinStrict glob = IO.glob fileExists = IO.fileExists getDataFileName = IO.getDataFileName getModificationTime = IO.getModificationTime getCommonState = PandocLua $ do Lua.getfield registryindex "PANDOC_STATE" forcePeek $ peekCommonState Lua.top `lastly` pop 1 putCommonState cst = PandocLua $ do pushCommonState cst Lua.setfield registryindex "PANDOC_STATE" logOutput = IO.logOutput -- | Retrieve a @'PandocError'@ from the Lua stack. popPandocError :: LuaE PandocError PandocError popPandocError = do errResult <- runPeek $ peekPandocError top `lastly` pop 1 case resultToEither errResult of Right x -> return x Left err -> return $ PandocLuaError (T.pack err) -- | Conversions between Lua errors and 'PandocError' exceptions. instance LuaError PandocError where popException = popPandocError pushException = pushPandocError luaException = PandocLuaError . T.pack pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Run.hs 0000644 0000000 0000000 00000004736 07346545000 017746 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {- | Module : Text.Pandoc.Lua.Run Copyright : Copyright © 2017-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Run code in the Lua interpreter. -} module Text.Pandoc.Lua.Run ( runLua , runLuaNoEnv , runLuaWith ) where import Control.Monad.Catch (try) import Control.Monad.Trans (MonadIO (..)) import HsLua as Lua hiding (try) import Text.Pandoc.Class (PandocMonad (..)) import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Global (Global (..), setGlobals) import Text.Pandoc.Lua.Init (initLua) import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua) -- | Run the Lua interpreter, using pandoc's default way of environment -- initialization. runLua :: (PandocMonad m, MonadIO m) => LuaE PandocError a -> m (Either PandocError a) runLua action = do runPandocLuaWith Lua.run . try $ do initLua liftPandocLua action runLuaWith :: (PandocMonad m, MonadIO m) => GCManagedState -> LuaE PandocError a -> m (Either PandocError a) runLuaWith luaState action = do runPandocLuaWith (withGCManagedState luaState) . try $ do initLua liftPandocLua action -- | Like 'runLua', but ignores all environment variables like @LUA_PATH@. runLuaNoEnv :: (PandocMonad m, MonadIO m) => LuaE PandocError a -> m (Either PandocError a) runLuaNoEnv action = do runPandocLuaWith Lua.run . try $ do liftPandocLua $ do -- This is undocumented, but works -- the code is adapted from the -- `lua.c` sources for the default interpreter. Lua.pushboolean True Lua.setfield Lua.registryindex "LUA_NOENV" initLua liftPandocLua action -- | Evaluate a @'PandocLua'@ computation, running all contained Lua -- operations. runPandocLuaWith :: (PandocMonad m, MonadIO m) => (forall b. LuaE PandocError b -> IO b) -> PandocLua a -> m a runPandocLuaWith runner pLua = do origState <- getCommonState let globals = defaultGlobals (result, newState) <- liftIO . runner . unPandocLua $ do putCommonState origState liftPandocLua $ setGlobals globals r <- pLua c <- getCommonState return (r, c) putCommonState newState return result -- | Global variables which should always be set. defaultGlobals :: [Global] defaultGlobals = [ PANDOC_API_VERSION , PANDOC_STATE , PANDOC_VERSION ] pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/SourcePos.hs 0000644 0000000 0000000 00000002453 07346545000 021116 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.SourcePos Copyright : © 2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Helper function to retrieve the 'SourcePos' in a Lua script. -} module Text.Pandoc.Lua.SourcePos ( luaSourcePos ) where import HsLua import Text.Parsec.Pos (SourcePos, newPos) import Text.Read (readMaybe) import qualified Data.Text as T import qualified HsLua.Core.Utf8 as UTF8 -- | Returns the current position in a Lua script. -- -- The reporting level is the level of the call stack, for which the -- position should be reported. There might not always be a position -- available, e.g., in C functions. luaSourcePos :: LuaError e => Int -- ^ reporting level -> LuaE e (Maybe SourcePos) luaSourcePos lvl = do -- reporting levels: -- 0: this hook, -- 1: userdata wrapper function for the hook, -- 2: warn, -- 3: function calling warn. where' lvl locStr <- UTF8.toText <$> tostring' top return $ do (prfx, sfx) <- T.breakOnEnd ":" <$> T.stripSuffix ": " locStr (source, _) <- T.unsnoc prfx line <- readMaybe (T.unpack sfx) -- We have no column information, so always use column 1 Just $ newPos (T.unpack source) line 1 pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Writer/ 0000755 0000000 0000000 00000000000 07346545000 020110 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Writer/Classic.hs 0000644 0000000 0000000 00000020640 07346545000 022027 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Text.Pandoc.Lua.Writer.Classic Copyright : Copyright (C) 2012-2024 John MacFarlane License : GNU GPL, version 2 or above Maintainer : John MacFarlane Stability : alpha Portability : portable Conversion of Pandoc documents using a \"classic\" custom Lua writer. -} module Text.Pandoc.Lua.Writer.Classic ( runCustom ) where import Control.Applicative (optional) import Control.Arrow ((***)) import Data.List (intersperse) import Data.Maybe (fromMaybe) import qualified Data.Text as T import Data.Text (Text, pack) import HsLua as Lua hiding (Operation (Div)) #if !MIN_VERSION_hslua(2,2,0) import HsLua.Aeson (peekViaJSON) #endif import Text.DocLayout (literal, render) import Text.DocTemplates (Context) import Text.Pandoc.Definition import Text.Pandoc.Lua.Marshal.Attr (pushAttributeList) import Text.Pandoc.Lua.Orphans () import Text.Pandoc.Options import Text.Pandoc.Templates (renderTemplate) import Text.Pandoc.Writers.Shared -- | List of key-value pairs that is pushed to Lua as AttributeList -- userdata. newtype AttributeList = AttributeList [(Text, Text)] instance Pushable AttributeList where push (AttributeList kvs) = pushAttributeList kvs attrToMap :: Attr -> AttributeList attrToMap (id',classes,keyvals) = AttributeList $ ("id", id') : ("class", T.unwords classes) : keyvals newtype Stringify a = Stringify a instance Pushable (Stringify Format) where push (Stringify (Format f)) = Lua.push (T.toLower f) instance Pushable (Stringify [Inline]) where push (Stringify ils) = Lua.push =<< inlineListToCustom ils instance Pushable (Stringify [Block]) where push (Stringify blks) = Lua.push =<< blockListToCustom blks instance Pushable (Stringify MetaValue) where push (Stringify (MetaMap m)) = Lua.push (fmap Stringify m) push (Stringify (MetaList xs)) = Lua.push (map Stringify xs) push (Stringify (MetaBool x)) = Lua.push x push (Stringify (MetaString s)) = Lua.push s push (Stringify (MetaInlines ils)) = Lua.push (Stringify ils) push (Stringify (MetaBlocks bs)) = Lua.push (Stringify bs) instance Pushable (Stringify Citation) where push (Stringify cit) = pushAsTable [ ("citationId", push . citationId) , ("citationPrefix", push . Stringify . citationPrefix) , ("citationSuffix", push . Stringify . citationSuffix) , ("citationMode", push . citationMode) , ("citationNoteNum", push . citationNoteNum) , ("citationHash", push . citationHash) ] cit -- | Key-value pair, pushed as a table with @a@ as the only key and @v@ as the -- associated value. newtype KeyValue a b = KeyValue (a, b) instance (Pushable a, Pushable b) => Pushable (KeyValue a b) where push (KeyValue (k, v)) = do Lua.newtable Lua.push k Lua.push v Lua.rawset (Lua.nth 3) -- | Convert Pandoc to custom markup using a classic Lua writer. runCustom :: LuaError e => WriterOptions -> Pandoc -> LuaE e Text runCustom opts doc@(Pandoc meta _) = do (body, context) <- docToCustom opts doc -- convert metavalues to a template context (variables) metaContext <- metaToContext opts (fmap (literal . pack) . blockListToCustom) (fmap (literal . pack) . inlineListToCustom) meta -- merge contexts from metadata and variables let renderContext = context <> metaContext return $ case writerTemplate opts of Nothing -> body Just tpl -> render Nothing $ renderTemplate tpl $ setField "body" body renderContext -- | Converts a Pandoc value to custom markup using a classic Lua writer. docToCustom :: forall e. LuaError e => WriterOptions -> Pandoc -> LuaE e (Text, Context Text) docToCustom opts (Pandoc (Meta metamap) blocks) = do body <- blockListToCustom blocks -- invoke doesn't work with multiple return values, so we have to call -- `Doc` manually. Lua.getglobal "Doc" -- function push body -- argument 1 push (fmap Stringify metamap) -- argument 2 push (writerVariables opts) -- argument 3 call 3 2 rendered <- peek (nth 2) -- first return value context <- forcePeek . optional $ peekViaJSON top -- snd return value return (rendered, fromMaybe mempty context) -- | Convert Pandoc block element to Custom. blockToCustom :: forall e. LuaError e => Block -- ^ Block element -> LuaE e String blockToCustom (Plain inlines) = invoke "Plain" (Stringify inlines) blockToCustom (Para [Image attr txt (src,tit)]) = invoke "CaptionedImage" src tit (Stringify txt) (attrToMap attr) blockToCustom (Para inlines) = invoke "Para" (Stringify inlines) blockToCustom (LineBlock linesList) = invoke "LineBlock" (map (Stringify) linesList) blockToCustom (RawBlock format str) = invoke "RawBlock" (Stringify format) str blockToCustom HorizontalRule = invoke "HorizontalRule" blockToCustom (Header level attr inlines) = invoke "Header" level (Stringify inlines) (attrToMap attr) blockToCustom (CodeBlock attr str) = invoke "CodeBlock" str (attrToMap attr) blockToCustom (BlockQuote blocks) = invoke "BlockQuote" (Stringify blocks) blockToCustom (Figure attr (Caption _ cbody) content) = invoke "Figure" (Stringify cbody) (Stringify content) (attrToMap attr) blockToCustom (Table _ blkCapt specs thead tbody tfoot) = let (capt, aligns, widths, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot aligns' = map show aligns capt' = Stringify capt headers' = map (Stringify) headers rows' = map (map (Stringify)) rows in invoke "Table" capt' aligns' widths headers' rows' blockToCustom (BulletList items) = invoke "BulletList" (map (Stringify) items) blockToCustom (OrderedList (num,sty,delim) items) = invoke "OrderedList" (map (Stringify) items) num (show sty) (show delim) blockToCustom (DefinitionList items) = invoke "DefinitionList" (map (KeyValue . (Stringify *** map (Stringify))) items) blockToCustom (Div attr items) = invoke "Div" (Stringify items) (attrToMap attr) -- | Convert list of Pandoc block elements to Custom. blockListToCustom :: forall e. LuaError e => [Block] -- ^ List of block elements -> LuaE e String blockListToCustom xs = do blocksep <- invoke "Blocksep" bs <- mapM blockToCustom xs return $ mconcat $ intersperse blocksep bs -- | Convert list of Pandoc inline elements to Custom. inlineListToCustom :: forall e. LuaError e => [Inline] -> LuaE e String inlineListToCustom lst = do xs <- mapM (inlineToCustom @e) lst return $ mconcat xs -- | Convert Pandoc inline element to Custom. inlineToCustom :: forall e. LuaError e => Inline -> LuaE e String inlineToCustom (Str str) = invoke "Str" str inlineToCustom Space = invoke "Space" inlineToCustom SoftBreak = invoke "SoftBreak" inlineToCustom (Emph lst) = invoke "Emph" (Stringify lst) inlineToCustom (Underline lst) = invoke "Underline" (Stringify lst) inlineToCustom (Strong lst) = invoke "Strong" (Stringify lst) inlineToCustom (Strikeout lst) = invoke "Strikeout" (Stringify lst) inlineToCustom (Superscript lst) = invoke "Superscript" (Stringify lst) inlineToCustom (Subscript lst) = invoke "Subscript" (Stringify lst) inlineToCustom (SmallCaps lst) = invoke "SmallCaps" (Stringify lst) inlineToCustom (Quoted SingleQuote lst) = invoke "SingleQuoted" (Stringify lst) inlineToCustom (Quoted DoubleQuote lst) = invoke "DoubleQuoted" (Stringify lst) inlineToCustom (Cite cs lst) = invoke "Cite" (Stringify lst) (map (Stringify) cs) inlineToCustom (Code attr str) = invoke "Code" str (attrToMap attr) inlineToCustom (Math DisplayMath str) = invoke "DisplayMath" str inlineToCustom (Math InlineMath str) = invoke "InlineMath" str inlineToCustom (RawInline format str) = invoke "RawInline" (Stringify format) str inlineToCustom LineBreak = invoke "LineBreak" inlineToCustom (Link attr txt (src,tit)) = invoke "Link" (Stringify txt) src tit (attrToMap attr) inlineToCustom (Image attr alt (src,tit)) = invoke "Image" (Stringify alt) src tit (attrToMap attr) inlineToCustom (Note contents) = invoke "Note" (Stringify contents) inlineToCustom (Span attr items) = invoke "Span" (Stringify items) (attrToMap attr) pandoc-lua-engine-0.5.1/src/Text/Pandoc/Lua/Writer/Scaffolding.hs 0000644 0000000 0000000 00000027514 07346545000 022674 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Lua.Writer.Scaffolding Copyright : © 2022-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Conversion of Pandoc documents using a custom Lua writer. -} module Text.Pandoc.Lua.Writer.Scaffolding ( pushWriterScaffolding ) where import Control.Monad ((<$!>), void) import Data.ByteString (ByteString) import Data.Data (dataTypeConstrs, dataTypeOf, showConstr, toConstr) import Data.Default (def) import Data.List (intersperse) import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.String (IsString (fromString)) import HsLua import HsLua.Module.DocLayout (peekDoc, pushDoc) import Text.DocLayout (Doc, blankline, render) import Text.DocTemplates (Context) import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError (..)) import Text.Pandoc.Options (WriterOptions (..), WrapOption(..)) import Text.Pandoc.Lua.PandocLua () import Text.Pandoc.Lua.Marshal.AST import Text.Pandoc.Lua.Marshal.Context (peekContext) import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions , pushWriterOptions) import Text.Pandoc.Templates (renderTemplate) import Text.Pandoc.Writers.Shared (metaToContext, setField) import qualified Data.Text as T import qualified Text.Pandoc.UTF8 as UTF8 -- | Convert Pandoc to custom markup. pushWriterScaffolding :: LuaE PandocError NumResults pushWriterScaffolding = do newtable *> pushWriterMT *> setmetatable (nth 2) writer <- toWriterTable top addField "Blocks" $ pushDocumentedFunction (blocksFn writer) addField "Inlines" $ pushDocumentedFunction (inlinesFn writer) addField "Block" $ newtable *> pushBlockMT writer *> setmetatable (nth 2) addField "Inline" $ newtable *> pushInlineMT writer *> setmetatable (nth 2) addField "Pandoc" $ pushDocumentedFunction $ lambda ### (\(Pandoc _ blks) -> do pushWriterTable writer getfield' top "Blocks" pushBlocks blks callTrace 1 1 pure (NumResults 1)) <#> parameter peekPandoc "Pandoc" "doc" "" =?> "rendered doc" freeWriter writer return 1 where blocksFn w = lambda ### (\blocks msep -> blockListToCustom w msep blocks) <#> parameter peekBlocks "Blocks" "blocks" "" <#> opt (parameter peekDocFuzzy "Doc" "sep" "") =#> functionResult pushDoc "Doc" "" inlinesFn w = lambda ### inlineListToCustom w <#> parameter peekInlines "Inlines" "inlines" "" =#> functionResult pushDoc "Doc" "" pushBlockMT writer = do newtable addField "__call" $ pushDocumentedFunction $ lambda ### blockToCustom <#> parameter peekWriter "table" "writer" "" <#> parameter peekBlockFuzzy "Block" "block" "" =#> functionResult pushDoc "Doc" "rendered blocks" addField "__index" $ -- lookup missing fields in the main Writer table pushWriterTable writer pushInlineMT writer = do newtable addField "__call" $ pushDocumentedFunction $ lambda ### inlineToCustom <#> parameter peekWriter "table" "writer" "" <#> parameter peekInlineFuzzy "Inline" "inline" "" =#> functionResult pushDoc "Doc" "rendered inline" addField "__index" $ do -- lookup missing fields in the main Writer table pushWriterTable writer pushWriterMT :: LuaE PandocError () pushWriterMT = do newtable addField "__call" $ pushDocumentedFunction $ lambda ### (\writer doc mopts -> runWriter writer doc mopts) <#> parameter peekWriter "table" "writer" "" <#> parameter peekPandoc "Pandoc" "doc" "" <#> opt (parameter peekWriterOptions "WriterOptions" "opts" "") =#> functionResult pushText "string" "rendered document" addField "__index" . pushDocumentedFunction $ lambda ### (\_writer key -> handleMissingField key) <#> parameter pure "table" "writer" "" <#> parameter (liftLua . tostring') "string" "key" "" =#> functionResult (const pushnil) "string" "" addField :: LuaError e => Name -> LuaE e a -> LuaE e () addField name action = do pushName name action rawset (nth 3) getfield' :: LuaError e => StackIndex -> Name -> LuaE e HsLua.Type getfield' idx name = do aidx <- absindex idx pushName name rawget aidx >>= \case TypeNil -> pop 1 *> getfield aidx name ty -> pure ty -- | A writer table is just an absolute stack index. newtype WriterTable = WriterTable Reference toWriterTable :: LuaError e => StackIndex -> LuaE e WriterTable toWriterTable idx = WriterTable <$!> do pushvalue idx ref registryindex peekWriter :: LuaError e => Peeker e WriterTable peekWriter = liftLua . toWriterTable pushWriterTable :: LuaError e => Pusher e WriterTable pushWriterTable (WriterTable wref) = void $ getref registryindex wref writerOptionsField :: Name writerOptionsField = "Pandoc Writer WriterOptions" freeWriter :: WriterTable -> LuaE e () freeWriter (WriterTable wref) = unref registryindex wref pushOpts :: LuaE PandocError () pushOpts = void $ getfield' registryindex writerOptionsField runWriter :: WriterTable -> Pandoc -> Maybe WriterOptions -> LuaE PandocError Text runWriter writer doc@(Pandoc meta _blks) mopts = do let opts = fromMaybe def mopts pushWriterOptions opts *> setfield registryindex writerOptionsField (body, mcontext) <- runPeek (pandocToCustom writer doc) >>= force . \case Failure msg contexts -> Failure (cleanupTrace msg) contexts s -> s -- convert metavalues to a template context (variables) defaultContext <- metaToContext opts (blockListToCustom writer Nothing) (inlineListToCustom writer) meta let context = setField "body" body $ fromMaybe defaultContext mcontext let colwidth = if writerWrapText opts == WrapAuto then Just $ writerColumns opts else Nothing return $ render colwidth $ case writerTemplate opts of Nothing -> body Just tpl -> renderTemplate tpl context -- | Keep exactly one traceback and clean it up. This wouldn't be -- necessary if the @pcallTrace@ function would do nothing whenever the -- error already included a trace, but that would require some bigger -- changes; removing the additional traces in this post-process step is -- much easier (for now). cleanupTrace :: ByteString -> ByteString cleanupTrace msg = UTF8.fromText . T.intercalate "\n" $ let tmsg = T.lines $ UTF8.toText msg traceStart = (== "stack traceback:") in case break traceStart tmsg of (x, t:traces) -> (x <>) . (t:) $ let (firstTrace, rest) = break traceStart traces isPeekContext = ("\twhile " `T.isPrefixOf`) isUnknownCFn = (== "\t[C]: in ?") in filter (not . isUnknownCFn) firstTrace <> filter isPeekContext rest _ -> tmsg -- | Pushes the field in the writer table. getWriterField :: LuaError e => WriterTable -> Name -> LuaE e HsLua.Type getWriterField writer name = do pushWriterTable writer getfield' top name <* remove (nth 2) -- | Looks up @Writer.subtable.field@; tries @Writer.field@ as a fallback if the -- subtable field is @nil@. getNestedWriterField :: LuaError e => WriterTable -> Name -> Name -> LuaE e HsLua.Type getNestedWriterField writer subtable field = do pushWriterTable writer getfield' top subtable >>= \case TypeNil -> TypeNil <$ remove (nth 2) -- remove Writer table _ -> getfield' top field -- remove Writer and subtable <* remove (nth 3) <* remove (nth 2) pandocToCustom :: WriterTable -> Pandoc -> Peek PandocError (Doc Text, Maybe (Context Text)) pandocToCustom writer doc = withContext "rendering Pandoc" $ do callStatus <- liftLua $ do getWriterField writer "Pandoc" pushPandoc doc pushOpts pcallTrace 2 2 case callStatus of OK -> ((,) <$> peekDocFuzzy (nth 2) <*> orNil peekContext top) `lastly` pop 2 _ -> failPeek =<< liftLua (tostring' top) blockToCustom :: WriterTable -> Block -> LuaE PandocError (Doc Text) blockToCustom writer blk = forcePeek $ renderBlock writer blk renderBlock :: WriterTable -> Block -> Peek PandocError (Doc Text) renderBlock writer blk = do let constrName = fromString . showConstr . toConstr $ blk withContext ("rendering Block `" <> constrName <> "`") $ liftLua (getNestedWriterField writer "Block" constrName) >>= \case TypeNil -> failPeek =<< typeMismatchMessage "function or Doc" top _ -> callOrDoc (pushBlock blk) inlineToCustom :: WriterTable -> Inline -> LuaE PandocError (Doc Text) inlineToCustom writer inln = forcePeek $ renderInline writer inln renderInline :: WriterTable -> Inline -> Peek PandocError (Doc Text) renderInline writer inln = do let constrName = fromString . showConstr . toConstr $ inln withContext ("rendering Inline `" <> constrName <> "`") $ do liftLua (getNestedWriterField writer "Inline" constrName) >>= \case TypeNil -> failPeek =<< typeMismatchMessage "function or Doc" top _ -> callOrDoc (pushInline inln) -- | If the value at the top of the stack can be called as a function, -- then push the element and writer options to the stack and call it; -- otherwise treat it as a plain Doc value callOrDoc :: LuaE PandocError () -> Peek PandocError (Doc Text) callOrDoc pushElement = do liftLua (ltype top) >>= \case TypeFunction -> peekCall _ -> liftLua (getmetafield top "__call") >>= \case TypeNil -> peekDocFuzzy top _ -> liftLua (pop 1) *> peekCall where peekCall :: Peek PandocError (Doc Text) peekCall = liftLua (pushElement *> pushOpts *> pcallTrace 2 1) >>= \case OK -> peekDocFuzzy top _ -> failPeek =<< liftLua (tostring' top) blockListToCustom :: WriterTable -> Maybe (Doc Text) -> [Block] -> LuaE PandocError (Doc Text) blockListToCustom writer msep blocks = forcePeek $ renderBlockList writer msep blocks inlineListToCustom :: WriterTable -> [Inline] -> LuaE PandocError (Doc Text) inlineListToCustom writer inlines = forcePeek $ renderInlineList writer inlines renderBlockList :: WriterTable -> Maybe (Doc Text) -> [Block] -> Peek PandocError (Doc Text) renderBlockList writer msep blocks = withContext "rendering Blocks" $ do let addSeps = intersperse $ fromMaybe blankline msep mconcat . addSeps <$> mapM (renderBlock writer) blocks renderInlineList :: WriterTable -> [Inline] -> Peek PandocError (Doc Text) renderInlineList writer inlines = withContext "rendering Inlines" $ do mconcat <$> mapM (renderInline writer) inlines orNil :: Peeker e a -> Peeker e (Maybe a) orNil p idx = liftLua (ltype idx) >>= \case TypeNil -> pure Nothing TypeNone -> pure Nothing _ -> Just <$> p idx peekDocFuzzy :: LuaError e => Peeker e (Doc Text) peekDocFuzzy idx = liftLua (ltype idx) >>= \case TypeTable -> mconcat <$!> peekList peekDoc idx _ -> peekDoc idx handleMissingField :: LuaError e => ByteString -> LuaE e () handleMissingField key' = let key = UTF8.toString key' blockNames = map (fromString . show) . dataTypeConstrs . dataTypeOf $ HorizontalRule inlineNames = map (fromString . show) . dataTypeConstrs . dataTypeOf $ Space mtypeName = case () of _ | key `elem` blockNames -> Just "Block" _ | key `elem` inlineNames -> Just "Inline" _ -> Nothing in case mtypeName of Just typeName -> failLua $ "No render function for " <> typeName <> " value " <> "'" <> key <> "';\ndefine a function `Writer." <> typeName <> "." <> key <> "` that returns " <> "a string or Doc." _ -> pure () pandoc-lua-engine-0.5.1/test/Tests/ 0000755 0000000 0000000 00000000000 07346545000 015255 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/Tests/Lua.hs 0000644 0000000 0000000 00000023014 07346545000 016332 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {- | Module : Tests.Lua Copyright : © 2017-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Portability : portable Unit and integration tests for pandoc's Lua subsystem. -} module Tests.Lua ( runLuaTest, tests ) where import HsLua as Lua hiding (Operation (Div), error) import System.FilePath ((>)) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit ((@=?), Assertion, HasCallStack, assertEqual, testCase) import Text.Pandoc.Arbitrary () import Text.Pandoc.Builder (bulletList, definitionList, displayMath, divWith, doc, doubleQuoted, emph, header, lineBlock, linebreak, math, orderedList, para, plain, rawBlock, singleQuoted, space, str, strong, HasMeta (setMeta)) import Text.Pandoc.Class ( runIOorExplode, setUserDataDir, setVerbosity ) import Text.Pandoc.Definition (Attr, Block (BlockQuote, Div, Para), Pandoc, Inline (Emph, Str), pandocTypesVersion) import Text.Pandoc.Error (PandocError (PandocLuaError)) import Text.Pandoc.Logging (Verbosity (ERROR)) import Text.Pandoc.Lua (Global (..), applyFilter, runLua, setGlobals) import Text.Pandoc.Options (def) import Text.Pandoc.Version (pandocVersionText) import qualified Control.Monad.Catch as Catch import qualified Data.Text as T import qualified Data.Text.Encoding as TE tests :: [TestTree] tests = [ testCase "macro expansion via filter" $ assertFilterConversion "a '{{helloworld}}' string is expanded" "strmacro.lua" (doc . para $ str "{{helloworld}}") (doc . para . emph $ str "Hello, World") , testCase "convert all plains to paras" $ assertFilterConversion "plains become para" "plain-to-para.lua" (doc $ bulletList [plain (str "alfa"), plain (str "bravo")]) (doc $ bulletList [para (str "alfa"), para (str "bravo")]) , testCase "convert display math to inline math" $ assertFilterConversion "display math becomes inline math" "math.lua" (doc $ para (displayMath "5+5")) (doc $ para (math "5+5")) , testCase "make hello world document" $ assertFilterConversion "Document contains 'Hello, World!'" "hello-world-doc.lua" (doc . para $ str "Hey!" <> linebreak <> str "What's up?") (doc . para $ str "Hello," <> space <> str "World!") , testCase "implicit doc filter" $ assertFilterConversion "Document contains 'Hello, World!'" "implicit-doc-filter.lua" (doc . plain $ linebreak) (doc . para $ str "Hello," <> space <> str "World!") , testCase "parse raw markdown blocks" $ assertFilterConversion "raw markdown block is converted" "markdown-reader.lua" (doc $ rawBlock "markdown" "*charly* **delta**") (doc . para $ emph "charly" <> space <> strong "delta") , testCase "allow shorthand functions for quote types" $ assertFilterConversion "single quoted becomes double quoted string" "single-to-double-quoted.lua" (doc . para . singleQuoted $ str "simple") (doc . para . doubleQuoted $ str "simple") , testCase "Count inlines via metatable catch-all" $ assertFilterConversion "filtering with metatable catch-all failed" "metatable-catch-all.lua" (doc . para $ "four words, three spaces") (doc . para $ str "7") , testCase "Count blocks via Block-specific catch-all" $ assertFilterConversion "filtering with Block catch-all failed" "block-count.lua" (doc $ para "one" <> para "two") (doc $ para "2") , testCase "Smart constructors" $ assertFilterConversion "smart constructors returned a wrong result" "smart-constructors.lua" (doc $ para "") (doc $ mconcat [ bulletList [para "Hello", para "World"] , definitionList [("foo", [para "placeholder"])] , lineBlock ["Moin", "Welt"] , orderedList [plain "one", plain "two"] ]) , testCase "Convert header upper case" $ assertFilterConversion "converting header to upper case failed" "uppercase-header.lua" (doc $ header 1 "les états-unis" <> para "text") (doc $ header 1 "LES ÉTATS-UNIS" <> para "text") , testCase "Attribute lists are convenient to use" $ let kv_before = [("one", "1"), ("two", "2"), ("three", "3")] kv_after = [("one", "eins"), ("three", "3"), ("five", "5")] in assertFilterConversion "Attr doesn't behave as expected" "attr-test.lua" (doc $ divWith ("", [], kv_before) (para "nil")) (doc $ divWith ("", [], kv_after) (para "nil")) , testCase "Filter list of inlines" $ assertFilterConversion "List of inlines" "inlines-filter.lua" (doc $ para ("Hello," <> linebreak <> "World! Wassup?")) (doc $ para "Hello, World! Wassup?") , testCase "Filter list of blocks" $ assertFilterConversion "List of blocks" "blocks-filter.lua" (doc $ para "one." <> para "two." <> para "three.") (doc $ plain "3") , testCase "Filter Meta" $ let setMetaBefore = setMeta "old" ("old" :: T.Text) . setMeta "bool" False setMetaAfter = setMeta "new" ("new" :: T.Text) . setMeta "bool" True in assertFilterConversion "Meta filtering" "meta.lua" (setMetaBefore . doc $ mempty) (setMetaAfter . doc $ mempty) , testCase "Script filename is set" $ assertFilterConversion "unexpected script name" "script-name.lua" (doc $ para "ignored") (doc $ para (str $ T.pack $ "lua" > "script-name.lua")) , testCase "Pandoc version is set" . runLuaTest $ do Lua.getglobal "PANDOC_VERSION" Lua.liftIO . assertEqual "pandoc version is wrong" (TE.encodeUtf8 pandocVersionText) =<< Lua.tostring' Lua.top , testCase "Pandoc types version is set" . runLuaTest $ do Lua.getglobal "PANDOC_API_VERSION" Lua.liftIO . assertEqual "pandoc-types version is wrong" pandocTypesVersion =<< Lua.peek Lua.top , testCase "require file" $ assertFilterConversion "requiring file failed" "require-file.lua" (doc $ para "ignored") (doc $ para (str . T.pack $ "lua" > "require-file.lua")) , testCase "Allow singleton inline in constructors" . runLuaTest $ do Lua.liftIO . assertEqual "Not the expected Emph" (Emph [Str "test"]) =<< do Lua.OK <- Lua.dostring "return pandoc.Emph" Lua.push @Inline (Str "test") Lua.call 1 1 Lua.peek @Inline top Lua.liftIO . assertEqual "Unexpected element" (Para [Str "test"]) =<< do Lua.getglobal' "pandoc.Para" Lua.pushString "test" Lua.call 1 1 Lua.peek @Block top Lua.liftIO . assertEqual "Unexptected element" (BlockQuote [Para [Str "foo"]]) =<< ( do Lua.getglobal' "pandoc.BlockQuote" Lua.push (Para [Str "foo"]) _ <- Lua.call 1 1 Lua.peek @Block Lua.top ) , testCase "Elements with Attr have `attr` accessor" . runLuaTest $ do Lua.push (Div ("hi", ["moin"], []) [Para [Str "ignored"]]) Lua.getfield Lua.top "attr" Lua.liftIO . assertEqual "no accessor" (("hi", ["moin"], []) :: Attr) =<< Lua.peek @Attr Lua.top , testCase "module `pandoc.system` is present" . runLuaTest $ do Lua.getglobal' "pandoc.system" ty <- Lua.ltype Lua.top Lua.liftIO $ assertEqual "module should be a table" Lua.TypeTable ty , testGroup "global modules" [ testCase "module 'lpeg' is loaded into a global" . runLuaTest $ do s <- Lua.dostring "assert(type(lpeg)=='table')" Lua.liftIO $ Lua.OK @=? s , testCase "module 're' is loaded into a global" . runLuaTest $ do s <- Lua.dostring "assert(type(re)=='table')" Lua.liftIO $ Lua.OK @=? s , testCase "module 'lpeg' is available via `require`" . runLuaTest $ do s <- Lua.dostring "package.path = ''; package.cpath = ''; require 'lpeg'" Lua.liftIO $ Lua.OK @=? s , testCase "module 're' is available via `require`" . runLuaTest $ do s <- Lua.dostring "package.path = ''; package.cpath = ''; require 're'" Lua.liftIO $ Lua.OK @=? s ] , testCase "informative error messages" . runLuaTest $ do Lua.pushboolean True -- Lua.newtable eitherPandoc <- Catch.try (peek @Pandoc Lua.top) case eitherPandoc of Left (PandocLuaError msg) -> do let expectedMsg = "Pandoc expected, got boolean\n" <> "\twhile retrieving Pandoc" Lua.liftIO $ assertEqual "unexpected error message" expectedMsg msg Left e -> error ("Expected a Lua error, but got " <> show e) Right _ -> error "Getting a Pandoc element from a bool should fail." ] assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion assertFilterConversion msg filterPath docIn expectedDoc = do actualDoc <- runIOorExplode $ do setUserDataDir (Just "../data") applyFilter def ["HTML"] ("lua" > filterPath) docIn assertEqual msg expectedDoc actualDoc runLuaTest :: HasCallStack => Lua.LuaE PandocError a -> IO a runLuaTest op = runIOorExplode $ do -- Disable printing of warnings on stderr: some tests will generate -- warnings, we don't want to see those messages. setVerbosity ERROR res <- runLua $ do setGlobals [ PANDOC_WRITER_OPTIONS def ] op case res of Left e -> error (show e) Right x -> return x pandoc-lua-engine-0.5.1/test/Tests/Lua/ 0000755 0000000 0000000 00000000000 07346545000 015776 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/Tests/Lua/Module.hs 0000644 0000000 0000000 00000003506 07346545000 017563 0 ustar 00 0000000 0000000 {- | Module : Tests.Lua.Module Copyright : © 2019-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Stability : alpha Portability : portable Lua module tests -} module Tests.Lua.Module (tests) where import System.FilePath ((>)) import Test.Tasty (TestName, TestTree) import Test.Tasty.Lua (testLuaFile) import Tests.Lua (runLuaTest) tests :: [TestTree] tests = [ testPandocLua "pandoc" ("lua" > "module" > "pandoc.lua") , testPandocLua "pandoc.List" ("lua" > "module" > "pandoc-list.lua") , testPandocLua "pandoc.format" ("lua" > "module" > "pandoc-format.lua") , testPandocLua "pandoc.image" ("lua" > "module" > "pandoc-image.lua") , testPandocLua "pandoc.json" ("lua" > "module" > "pandoc-json.lua") , testPandocLua "pandoc.log" ("lua" > "module" > "pandoc-log.lua") , testPandocLua "pandoc.mediabag" ("lua" > "module" > "pandoc-mediabag.lua") , testPandocLua "pandoc.path" ("lua" > "module" > "pandoc-path.lua") , testPandocLua "pandoc.structure" ("lua" > "module" > "pandoc-structure.lua") , testPandocLua "pandoc.template" ("lua" > "module" > "pandoc-template.lua") , testPandocLua "pandoc.text" ("lua" > "module" > "pandoc-text.lua") , testPandocLua "pandoc.types" ("lua" > "module" > "pandoc-types.lua") , testPandocLua "pandoc.utils" ("lua" > "module" > "pandoc-utils.lua") , testPandocLua "globals" ("lua" > "module" > "globals.lua") ] testPandocLua :: TestName -> FilePath -> TestTree testPandocLua = testLuaFile runLuaTest pandoc-lua-engine-0.5.1/test/Tests/Lua/Reader.hs 0000644 0000000 0000000 00000002227 07346545000 017537 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {- | Module : Tests.Lua.Reader Copyright : © 2022-2024 Albert Krewinkel License : GPL-2.0-or-later Maintainer : Albert Krewinkel Tests for custom Lua readers. -} module Tests.Lua.Reader (tests) where import Control.Arrow ((>>>)) import Data.Char (chr) import Data.Default (Default (def)) import Text.Pandoc.Class (runIOorExplode) import Text.Pandoc.Lua (loadCustom) import Text.Pandoc.Readers (Reader (ByteStringReader)) import Text.Pandoc.Scripting (customReader) import Test.Tasty (TestTree) import Test.Tasty.HUnit ((@?=), testCase) import qualified Data.ByteString.Lazy as BL import qualified Data.Text as T import qualified Text.Pandoc.Builder as B tests :: [TestTree] tests = [ testCase "read binary to code block" $ do input <- BL.readFile "bytestring.bin" doc <- runIOorExplode $ loadCustom "bytestring-reader.lua" >>= (customReader >>> \case Just (ByteStringReader f) -> f def input _ -> error "Expected a bytestring reader") let bytes = mconcat $ map (B.str . T.singleton . chr) [0..255] doc @?= B.doc (B.plain bytes) ] pandoc-lua-engine-0.5.1/test/Tests/Lua/Writer.hs 0000644 0000000 0000000 00000007355 07346545000 017620 0 ustar 00 0000000 0000000 {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Tests.Lua.Writer Copyright : © 2019-2024 Albert Krewinkel License : GNU GPL, version 2 or above Maintainer : Albert Krewinkel Tests for custom Lua writers. -} module Tests.Lua.Writer (tests) where import Data.Default (Default (def)) import Data.Maybe (fromMaybe) import Text.Pandoc.Class (runIOorExplode, readFileStrict) import Text.Pandoc.Extensions (Extension (..), extensionsFromList) import Text.Pandoc.Format (ExtensionsDiff (..), FlavoredFormat (..), applyExtensionsDiff) import Text.Pandoc.Lua (loadCustom) import Text.Pandoc.Options (WriterOptions (..)) import Text.Pandoc.Readers (readNative) import Text.Pandoc.Scripting (CustomComponents (..)) import Text.Pandoc.Writers (Writer (ByteStringWriter, TextWriter)) import Test.Tasty (TestTree) import Test.Tasty.Golden (goldenVsString) import Test.Tasty.HUnit (testCase, (@?=)) import qualified Data.ByteString.Lazy as BL import qualified Text.Pandoc.Builder as B import qualified Text.Pandoc.UTF8 as UTF8 tests :: [TestTree] tests = [ goldenVsString "default testsuite" "writer.custom" (runIOorExplode $ do source <- UTF8.toText <$> readFileStrict "testsuite.native" doc <- readNative def source txt <- customWriter <$> loadCustom "sample.lua" >>= \case Just (TextWriter f) -> f def doc _ -> error "Expected a text writer" pure $ BL.fromStrict (UTF8.fromText txt)) , goldenVsString "tables testsuite" "tables.custom" (runIOorExplode $ do source <- UTF8.toText <$> readFileStrict "tables.native" doc <- readNative def source txt <- writeCustom "sample.lua" >>= \case (TextWriter f, _, _) -> f def doc _ -> error "Expected a text writer" pure $ BL.fromStrict (UTF8.fromText txt)) , goldenVsString "bytestring writer" "bytestring.bin" (runIOorExplode $ writeCustom "bytestring.lua" >>= \case (ByteStringWriter f, _, _) -> f def mempty _ -> error "Expected a bytestring writer") , goldenVsString "template" "writer-template.out.txt" (runIOorExplode $ do (_, _, template) <- writeCustom "writer-template.lua" pure . BL.fromStrict . UTF8.fromText $ fromMaybe "" template) , testCase "preset extensions" $ do let format = FlavoredFormat "extensions.lua" mempty result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case (TextWriter write, extsConf, _) -> do exts <- applyExtensionsDiff extsConf format write def{writerExtensions = exts} (B.doc mempty) _ -> error "Expected a text writer" result @?= "smart extension is enabled;\ncitations extension is disabled\n" , testCase "modified extensions" $ do let ediff = ExtensionsDiff { extsToEnable = extensionsFromList [Ext_citations] , extsToDisable = mempty } let format = FlavoredFormat "extensions.lua" ediff result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case (TextWriter write, extsConf, _) -> do exts <- applyExtensionsDiff extsConf format write def{writerExtensions = exts} (B.doc mempty) _ -> error "Expected a text writer" result @?= "smart extension is enabled;\ncitations extension is enabled\n" ] where writeCustom fp = do components <- loadCustom fp let exts = fromMaybe mempty (customExtensions components) case customWriter components of Nothing -> error "Expected a writer to be defined" Just w -> return (w, exts, customTemplate components) pandoc-lua-engine-0.5.1/test/ 0000755 0000000 0000000 00000000000 07346545000 014153 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/bytestring-reader.lua 0000644 0000000 0000000 00000000332 07346545000 020306 0 ustar 00 0000000 0000000 function ByteStringReader (input, opts) local chars = pandoc.List{} for i = 1, #input do chars:insert(utf8.char(input:byte(i,i))) end return pandoc.Pandoc(pandoc.Plain(pandoc.Str(table.concat(chars)))) end pandoc-lua-engine-0.5.1/test/bytestring.bin 0000644 0000000 0000000 00000000400 07346545000 017031 0 ustar 00 0000000 0000000 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ pandoc-lua-engine-0.5.1/test/bytestring.lua 0000644 0000000 0000000 00000000241 07346545000 017045 0 ustar 00 0000000 0000000 function ByteStringWriter (doc, opts) local buffer = {} for i=0, 255 do table.insert(buffer, string.char(i)) end return table.concat(buffer, '') end pandoc-lua-engine-0.5.1/test/extensions.lua 0000644 0000000 0000000 00000000506 07346545000 017056 0 ustar 00 0000000 0000000 function Writer (doc, opts) local output = 'smart extension is %s;\ncitations extension is %s\n' local status = function (ext) return opts.extensions:includes(ext) and 'enabled' or 'disabled' end return output:format(status('smart'), status('citations')) end Extensions = { smart = true, citations = false, } pandoc-lua-engine-0.5.1/test/lua/ 0000755 0000000 0000000 00000000000 07346545000 014734 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/lua/attr-test.lua 0000644 0000000 0000000 00000000256 07346545000 017371 0 ustar 00 0000000 0000000 function Div (div) div.attributes.five = ("%d"):format(div.attributes.two + div.attributes.three) div.attributes.two = nil div.attributes.one = "eins" return div end pandoc-lua-engine-0.5.1/test/lua/block-count.lua 0000644 0000000 0000000 00000000263 07346545000 017660 0 ustar 00 0000000 0000000 local num_blocks = 0 function Block(el) num_blocks = num_blocks + 1 end function Pandoc(blocks, meta) return pandoc.Pandoc { pandoc.Para{pandoc.Str(num_blocks)} } end pandoc-lua-engine-0.5.1/test/lua/blocks-filter.lua 0000644 0000000 0000000 00000000512 07346545000 020175 0 ustar 00 0000000 0000000 function Blocks (blks) -- verify that this looks like a `pandoc.List` if not blks.find or not blks.map or not blks.filter then error("table doesn't seem to be an instance of pandoc.List") end -- return plain block containing the number of elements in the list return {pandoc.Plain {pandoc.Str(tostring(#blks))}} end pandoc-lua-engine-0.5.1/test/lua/hello-world-doc.lua 0000644 0000000 0000000 00000000362 07346545000 020433 0 ustar 00 0000000 0000000 return { { Pandoc = function(doc) local meta = {} local hello = { pandoc.Str "Hello,", pandoc.Space(), pandoc.Str "World!" } local blocks = { pandoc.Para(hello) } return pandoc.Pandoc(blocks, meta) end } } pandoc-lua-engine-0.5.1/test/lua/implicit-doc-filter.lua 0000644 0000000 0000000 00000000306 07346545000 021276 0 ustar 00 0000000 0000000 function Pandoc (doc) local meta = {} local hello = { pandoc.Str "Hello,", pandoc.Space(), pandoc.Str "World!" } local blocks = { pandoc.Para(hello) } return pandoc.Pandoc(blocks, meta) end pandoc-lua-engine-0.5.1/test/lua/inlines-filter.lua 0000644 0000000 0000000 00000001021 07346545000 020355 0 ustar 00 0000000 0000000 function isWorldAfterSpace (fst, snd) return fst and fst.t == 'LineBreak' and snd and snd.t == 'Str' and snd.text == 'World!' end function Inlines (inlns) -- verify that this looks like a `pandoc.List` if not inlns.find or not inlns.map or not inlns.filter then error("table doesn't seem to be an instance of pandoc.List") end -- Remove spaces before string "World" for i = #inlns-1,1,-1 do if isWorldAfterSpace(inlns[i], inlns[i+1]) then inlns[i] = pandoc.Space() end end return inlns end pandoc-lua-engine-0.5.1/test/lua/markdown-reader.lua 0000644 0000000 0000000 00000000336 07346545000 020523 0 ustar 00 0000000 0000000 return { { RawBlock = function (elem) if elem.format == "markdown" then local pd = pandoc.read(elem.text, "markdown") return pd.blocks[1] else return elem end end, } } pandoc-lua-engine-0.5.1/test/lua/math.lua 0000644 0000000 0000000 00000000245 07346545000 016371 0 ustar 00 0000000 0000000 return { { Math = function (elem) if elem.mathtype == "DisplayMath" then elem.mathtype = "InlineMath" end return elem end, } } pandoc-lua-engine-0.5.1/test/lua/meta.lua 0000644 0000000 0000000 00000000156 07346545000 016367 0 ustar 00 0000000 0000000 function Meta (meta) meta.old = nil meta.new = "new" meta.bool = (meta.bool == false) return meta end pandoc-lua-engine-0.5.1/test/lua/metatable-catch-all.lua 0000644 0000000 0000000 00000000542 07346545000 021224 0 ustar 00 0000000 0000000 local num_inlines = 0 function catch_all(el) if el.tag and pandoc.Inline.constructor[el.tag] then num_inlines = num_inlines + 1 end end function Pandoc(blocks, meta) return pandoc.Pandoc { pandoc.Para{pandoc.Str(num_inlines)} } end return { setmetatable( {Pandoc = Pandoc}, {__index = function(_) return catch_all end} ) } pandoc-lua-engine-0.5.1/test/lua/module/ 0000755 0000000 0000000 00000000000 07346545000 016221 5 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/lua/module/globals.lua 0000644 0000000 0000000 00000013463 07346545000 020356 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local test = tasty.test_case local group = tasty.test_group local assert = tasty.assert -- These tests exist mainly to catch changes to the JSON representation of -- WriterOptions and its components. UPDATE THE DOCS if anything changes. return { group 'PANDOC_WRITER_OPTIONS' { test('chunk_template', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.chunk_template), 'string') end), test('cite_method', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.cite_method), 'string') end), test('columns', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.columns), 'number') end), test('dpi', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.dpi), 'number') end), test('email_obfuscation', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.email_obfuscation), 'string') end), test('split_level', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.split_level), 'number') end), test('epub_fonts', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_fonts), 'table') end), test('epub_metadata', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_metadata), 'nil') end), test('epub_subdirectory', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_subdirectory), 'string') end), test('extensions', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.extensions), 'table') for _, v in ipairs(PANDOC_WRITER_OPTIONS.extensions) do assert.are_equal(type(v), 'string') end end), test('highlight_method', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.highlight_method), 'string') end), test('html_math_method', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.html_math_method), 'string') end), test('html_q_tags', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.html_q_tags), 'boolean') end), test('identifier_prefix', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.identifier_prefix), 'string') end), test('incremental', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.incremental), 'boolean') end), test('number_offset', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.number_offset), 'table') for _, v in ipairs(PANDOC_WRITER_OPTIONS.number_offset) do assert.are_equal(type(v), 'number') end end), test('number_sections', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.number_sections), 'boolean') end), test('prefer_ascii', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.prefer_ascii), 'boolean') end), test('reference_doc', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_doc), 'nil') end), test('reference_links', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_links), 'boolean') end), test('reference_location', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_location), 'string') end), test('section_divs', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.section_divs), 'boolean') end), test('setext_headers', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.setext_headers), 'boolean') end), test('slide_level', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.slide_level), 'nil') end), test('tab_stop', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.tab_stop), 'number') end), test('table_of_contents', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.table_of_contents), 'boolean') end), test('toc_depth', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.toc_depth), 'number') end), test('top_level_division', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.top_level_division), 'string') end), test('variables', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.variables), 'table') end), test('wrap_text', function () assert.are_equal(type(PANDOC_WRITER_OPTIONS.wrap_text), 'string') end), }, group 'PANDOC_STATE' { test('is a table object', function () assert.are_equal(type(PANDOC_STATE), 'table') end), test('has property "input_files"', function () assert.are_equal(type(PANDOC_STATE.input_files), 'table') end), test('has optional property "output_file"', function () -- property may be nil if PANDOC_STATE.output_file then assert.are_equal(type(PANDOC_STATE.output_file), 'string') end end), test('has property "log"', function () assert.are_equal(type(PANDOC_STATE.log), 'table') end), test('has property "request_headers"', function () assert.are_equal(type(PANDOC_STATE.request_headers), 'table') end), test('has property "resource_path"', function () assert.are_equal(type(PANDOC_STATE.resource_path), 'table') end), test('has optional property "source_url"', function () if PANDOC_STATE.source_url then assert.are_equal(type(PANDOC_STATE.source_url), 'string') end end), test('has property "trace"', function () assert.are_equal(type(PANDOC_STATE.trace), 'boolean') end), test('has optional property "user_data_dir"', function () if PANDOC_STATE.user_data_dir then assert.are_equal(type(PANDOC_STATE.user_data_dir), 'string') end end), test('has property "verbosity"', function () assert.are_equal(type(PANDOC_STATE.verbosity), 'string') end), test('can be deleted without breaking PandocLua monad functions', function() local state = PANDOC_STATE PANDOC_STATE = nil assert.is_nil(pandoc.mediabag.lookup('does-not-exist')) PANDOC_STATE = state end), }, } pandoc-lua-engine-0.5.1/test/lua/module/include.tex 0000644 0000000 0000000 00000000011 07346545000 020356 0 ustar 00 0000000 0000000 included pandoc-lua-engine-0.5.1/test/lua/module/pandoc-format.lua 0000644 0000000 0000000 00000002402 07346545000 021454 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local test = tasty.test_case local group = tasty.test_group local assert = tasty.assert local format = require 'pandoc.format' return { group 'default_extensions' { test('docx', function () local docx_default_exts = { 'auto_identifiers', } assert.are_same(format.default_extensions('docx'), docx_default_exts) end), }, group 'all_extensions' { test('docx', function () local docx_default_exts = { 'ascii_identifiers', 'auto_identifiers', 'citations', 'east_asian_line_breaks', 'empty_paragraphs', 'gfm_auto_identifiers', 'native_numbering', 'styles', } assert.are_same(format.all_extensions('docx'), docx_default_exts) end), }, group 'extensions' { test('org', function () local org_default_exts = { ascii_identifiers = false, auto_identifiers = true, citations = true, east_asian_line_breaks = false, fancy_lists = false, gfm_auto_identifiers = false, smart = false, smart_quotes = false, special_strings = true, task_lists = true, } assert.are_same(format.extensions 'org', org_default_exts) end), }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-image.lua 0000644 0000000 0000000 00000003247 07346545000 021256 0 ustar 00 0000000 0000000 -- -- Tests for the system module -- local image = require 'pandoc.image' local tasty = require 'tasty' local group = tasty.test_group local test = tasty.test_case local assert = tasty.assert local svg_image = [==[ ]==] return { -- Check existence of static fields group 'static fields' { }, group 'size' { test('returns a table', function () local imgsize = { width = 70, height = 70, dpi_horz = 96, dpi_vert = 96, } assert.are_same(image.size(svg_image), imgsize) end), test('fails on faulty eps', function () assert.error_matches( function () image.size('%!PS EPSF') end, 'could not determine EPS size' ) end), test('fails if input is not an image', function () assert.error_matches( function () image.size('not an image') end, 'could not determine image type' ) end), test('respects the dpi setting', function () local imgsize = { width = 70, height = 70, dpi_horz = 300, dpi_vert = 300, } assert.are_same(image.size(svg_image, {dpi=300}), imgsize) end), }, group 'format' { test('SVG', function () assert.are_equal(image.format(svg_image), 'svg') end), test('returns nil if input is not an image', function () assert.is_nil(image.format('not an image')) end), }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-json.lua 0000644 0000000 0000000 00000006164 07346545000 021146 0 ustar 00 0000000 0000000 -- -- Tests for the system module -- local pandoc = require 'pandoc' local json = require 'pandoc.json' local tasty = require 'tasty' local group = tasty.test_group local test = tasty.test_case local assert = tasty.assert return { -- Check existence of static fields group 'static fields' { test('null', function () assert.are_equal(type(json.null), 'userdata') end), }, group 'encode' { test('string', function () assert.are_equal(json.encode 'one\ntwo', '"one\\ntwo"') end), test('null', function () assert.are_equal(json.encode(json.null), 'null') end), test('number', function () assert.are_equal(json.encode(42), '42') end), test('object', function () assert.are_same(json.encode{a = 5}, '{"a":5}') end), test('object with metamethod', function () local obj = setmetatable( {title = 23}, { __tojson = function (_) return '"Nichts ist so wie es scheint"' end } ) assert.are_same(json.encode(obj), [["Nichts ist so wie es scheint"]]) end), test('pandoc.List', function () local list = pandoc.List {'foo', 'bar', 'baz'} assert.are_equal( json.encode(list), '["foo","bar","baz"]' ) end), test('Inline (Space)', function () assert.are_equal( json.encode(pandoc.Space()), '{"t":"Space"}' ) end), test('Block (HorizontalRule)', function () assert.are_equal( json.encode(pandoc.HorizontalRule()), '{"t":"HorizontalRule"}' ) end), test('Inlines list', function () assert.are_equal( json.encode(pandoc.Inlines{pandoc.Space()}), '[{"t":"Space"}]' ) end), test('Pandoc', function () assert.are_equal( type(json.encode(pandoc.Pandoc{'Hello from Lua!'})), 'string' ) end), test('Nested Inline', function () assert.are_equal( json.encode({spc = pandoc.Space()}), '{"spc":{"t":"Space"}}' ) end) }, group 'decode' { test('string', function () assert.are_equal(json.decode '"one\\ntwo"', 'one\ntwo') end), test('null', function () assert.are_equal(json.decode 'null', json.null) end), test('number', function () assert.are_equal(json.decode '42', 42) end), test('object', function () assert.are_same(json.decode '{"a":5}', {a = 5}) end), test('list of strings', function () assert.are_equal(json.decode '["foo", "bar"]', pandoc.List{"foo", "bar"}) end), test('Inline (Space)', function () assert.are_equal(json.decode '{"t":"Space"}', pandoc.Space()) end), test('Inline (Str)', function () assert.are_equal(json.decode '{"t":"Str", "c":"a"}', pandoc.Str 'a') end), test('disabled AST check', function () assert.are_same( json.decode('{"t":"Str", "c":"a"}', false), {t = 'Str', c = 'a'} ) end), test('Inlines list', function () assert.are_equal( json.decode '[{"t":"Space"}]', pandoc.Inlines{pandoc.Space()} ) end) }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-list.lua 0000644 0000000 0000000 00000011733 07346545000 021146 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local List = require 'pandoc.List' local assert = tasty.assert local test = tasty.test_case local group = tasty.test_group return { group 'List as function' { test('equivalent to List:new', function (x) local new = List:new {'ramen'} local list = List {'ramen'} assert.are_same(new, list) assert.are_equal(getmetatable(new), getmetatable(list)) end) }, group 'clone' { test('changing the clone does not affect original', function () local orig = List:new {23, 42} local copy = orig:clone() copy[1] = 5 assert.are_same({23, 42}, orig) assert.are_same({5, 42}, copy) end), test('result is a list', function () local orig = List:new {23, 42} assert.are_equal(List, getmetatable(orig:clone())) end), }, group 'extend' { test('extends list with other list', function () local primes = List:new {2, 3, 5, 7} primes:extend {11, 13, 17} assert.are_same({2, 3, 5, 7, 11, 13, 17}, primes) end) }, group 'filter' { test('keep elements for which property is truthy', function () local is_small_prime = function (x) return List.includes({2, 3, 5, 7}, x) end local numbers = List:new {4, 7, 2, 9, 5, 11} assert.are_same({7, 2, 5}, numbers:filter(is_small_prime)) end), }, group 'find' { test('returns element and index if found', function () local list = List:new {5, 23, 71} local elem, idx = list:find(71) assert.are_same(71, elem) assert.are_same(3, idx) end), test('respects start index', function () local list = List:new {19, 23, 29, 71} assert.are_equal(23, list:find(23, 1)) assert.are_equal(23, list:find(23, 2)) assert.is_nil(list:find(23, 3)) end), test('returns nil if element not found', function () assert.is_nil((List:new {18, 20, 22, 0, 24}):find('0')) end), }, group 'find_if' { test('returns element and index if found', function () local perm_prime = List:new {2, 3, 5, 7, 11, 13, 17, 31, 37, 71} local elem, idx = perm_prime:find_if(function (x) return x >= 10 end) assert.are_same(11, elem) assert.are_same(5, idx) end), test('returns nil if element not found', function () local is_null = function (n) return List.includes({23,35,46,59}, n) end assert.is_nil((List:new {18, 20, 22, 24, 27}):find_if(is_null)) end), }, group 'includes' { test('finds elements in list', function () local lst = List:new {'one', 'two', 'three'} assert.is_truthy(lst:includes('one')) assert.is_truthy(lst:includes('two')) assert.is_truthy(lst:includes('three')) assert.is_falsy(lst:includes('four')) end) }, group 'insert' { test('insert value at end of list.', function () local count_norsk = List {'en', 'to', 'tre'} count_norsk:insert('fire') assert.are_same({'en', 'to', 'tre', 'fire'}, count_norsk) end), test('insert value in the middle of list.', function () local count_norsk = List {'fem', 'syv'} count_norsk:insert(2, 'seks') assert.are_same({'fem', 'seks', 'syv'}, count_norsk) end) }, group 'map' { test('applies function to elements', function () local primes = List:new {2, 3, 5, 7} local squares = primes:map(function (x) return x^2 end) assert.are_same({4, 9, 25, 49}, squares) end), test('leaves original list unchanged', function () local primes = List:new {2, 3, 5, 7} local squares = primes:map(function (x) return x^2 end) assert.are_same({2, 3, 5, 7}, primes) end) }, group 'new' { test('make table usable as list', function () local test = List:new{1, 1, 2, 3, 5} assert.are_same( {1, 1, 4, 9, 25}, test:map(function (x) return x^2 end) ) end), test('return empty list if no argument is given', function () assert.are_same({}, List:new()) end), test('metatable of result is pandoc.List', function () local test = List:new{5} assert.are_equal(List, getmetatable(test)) end) }, group 'remove' { test('remove value at end of list.', function () local understand = List {'jeg', 'forstår', 'ikke'} local norsk_not = understand:remove() assert.are_same({'jeg', 'forstår'}, understand) assert.are_equal('ikke', norsk_not) end), test('remove value at beginning of list.', function () local count_norsk = List {'en', 'to', 'tre'} count_norsk:remove(1) assert.are_same({'to', 'tre'}, count_norsk) end) }, group 'sort' { test('sort numeric list', function () local numbers = List {71, 5, -1, 42, 23, 0, 1} numbers:sort() assert.are_same({-1, 0, 1, 5, 23, 42, 71}, numbers) end), test('reverse-sort numeric', function () local numbers = List {71, 5, -1, 42, 23, 0, 1} numbers:sort(function (x, y) return x > y end) assert.are_same({71, 42, 23, 5, 1, 0, -1}, numbers) end) }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-log.lua 0000644 0000000 0000000 00000004662 07346545000 020757 0 ustar 00 0000000 0000000 -- -- Tests for the pandoc.log module -- -- ========================================= -- PLEASE BE CAREFUL WHEN UPDATING THE TESTS -- ========================================= -- -- Some tests here are very, very fragile, as their correctness depends on the -- correct line number in this file. local log = require 'pandoc.log' local json = require 'pandoc.json' local tasty = require 'tasty' local group = tasty.test_group local test = tasty.test_case local assert = tasty.assert return { group 'info' { test('is a function', function () assert.are_equal(type(log.info), 'function') end), test('reports a warning', function () log.info('info test') local msg = json.decode(json.encode(PANDOC_STATE.log:at(-1))) assert.are_equal(msg.message, 'info test') assert.are_equal(msg.type, 'ScriptingInfo') end), test('info includes the correct number', function () log.info('line number test') local msg = json.decode(json.encode(PANDOC_STATE.log:at(-1))) -- THIS NEEDS UPDATING if lines above are shifted. assert.are_equal(msg.line, 30) end), }, group 'warn' { test('is a function', function () assert.are_equal(type(log.warn), 'function') end), test('reports a warning', function () log.warn('testing') local msg = json.decode(json.encode(PANDOC_STATE.log:at(-1))) assert.are_equal(msg.message, 'testing') assert.are_equal(msg.type, 'ScriptingWarning') end), }, group 'silence' { test('prevents info from being logged', function () local current_messages = PANDOC_STATE.log log.silence(log.info, 'Just so you know') assert.are_same(#current_messages, #PANDOC_STATE.log) for i = 1, #current_messages do assert.are_equal( json.encode(current_messages[i]), json.encode(PANDOC_STATE.log[i]) ) end end), test('returns the messages raised by the called function', function () local msgs = log.silence(log.info, 'Just so you know') local msg = json.decode(json.encode(msgs[1])) assert.are_equal(msg.message, 'Just so you know') end), test('returns function application results as additional return values', function () local l, x, y = log.silence(function (a, b) return b, a + b end, 5, 8) assert.are_same(l, {}) assert.are_equal(x, 8) assert.are_equal(y, 13) end ) } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-mediabag.lua 0000644 0000000 0000000 00000007067 07346545000 021731 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local test = tasty.test_case local group = tasty.test_group local assert = tasty.assert local mediabag = require 'pandoc.mediabag' return { group 'insert' { test('insert adds an item to the mediabag', function () local fp = "media/hello.txt" local mt = "text/plain" local contents = "Hello, World!" assert.are_same(mediabag.list(), {}) mediabag.insert(fp, mt, contents) assert.are_same( mediabag.list(), {{['path'] = fp, ['type'] = mt, ['length'] = 13}} ) mediabag.empty() -- clean up end), test('is idempotent', function () local fp = "media/hello.txt" local mt = "text/plain" local contents = "Hello, World!" mediabag.insert(fp, mt, contents) mediabag.insert(fp, mt, contents) assert.are_same( mediabag.list(), {{['path'] = fp, ['type'] = mt, ['length'] = 13}} ) mediabag.empty() -- clean up end), }, group 'delete' { test('removes an item', function () assert.are_same(mediabag.list(), {}) mediabag.insert('test.html', 'text/html', '') mediabag.insert('test.css', 'text/plain', 'aside { color: red; }') assert.are_equal(#mediabag.list(), 2) mediabag.delete('test.html') assert.are_same( mediabag.list(), {{['path'] = 'test.css', ['type'] = 'text/plain', ['length'] = 21}} ) mediabag.empty() -- clean up end), }, group 'fetch' { test('populates media bag', function () local filename = 'lua/module/sample.svg' local mime, contents = mediabag.fetch(filename) assert.are_equal(mime, 'image/svg+xml') assert.are_equal(contents:sub(1,5), 'Really?'}, ['test.css'] = {'text/plain', 'aside { color: red; }'}, ['test.js'] = {'application/javascript', 'alert("HI MOM!")'} } -- fill mediabag for name, v in pairs(input_items) do mediabag.insert(name, v[1], v[2]) end local seen_items = {} for fp, mt, c in mediabag.items() do seen_items[fp] = {mt, c} end assert.are_same(seen_items, input_items) mediabag.empty() -- clean up end) }, group 'lookup' { test('returns MIME type and contents', function () mediabag.insert('test.html', 'text/html', '') local mime, contents = mediabag.lookup('test.html') assert.are_equal(mime, 'text/html') assert.are_equal(contents, '') mediabag.empty() -- clean up end), }, group 'make_data_uri' { test('returns a data URI', function () local uri = mediabag.make_data_uri('text/plain', 'foo') assert.are_equal(uri:sub(1,5), 'data:') end), test('URI specifies the given MIME type', function () local mimetype = 'text/plain' local uri = mediabag.make_data_uri(mimetype, 'foo') assert.are_equal(uri:sub(6, 5 + #mimetype), mimetype) end), } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-path.lua 0000644 0000000 0000000 00000002133 07346545000 021121 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local path = require 'pandoc.path' local assert = tasty.assert local test = tasty.test_case local group = tasty.test_group return { group 'path separator' { test('is string', function () assert.are_same(type(path.separator), 'string') end), test('is slash or backslash', function () assert.is_truthy(path.separator:match '^[/\\]$') end), }, group 'search path separator' { test('is string', function () assert.are_same(type(path.search_path_separator), 'string') end), test('is colon or semicolon', function () assert.is_truthy(path.search_path_separator:match '^[:;]$') end) }, group 'module' { test('check function existence', function () local functions = { 'directory', 'filename', 'is_absolute', 'is_relative', 'join', 'make_relative', 'normalize', 'split', 'split_extension', 'split_search_path', } for _, f in ipairs(functions) do assert.are_equal(type(path[f]), 'function') end end) } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-structure.lua 0000644 0000000 0000000 00000013732 07346545000 022234 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local structure = require 'pandoc.structure' local path = require 'pandoc.path' local system = require 'pandoc.system' local assert = tasty.assert local test = tasty.test_case local group = tasty.test_group return { test('is table', function () assert.are_equal(type(structure), 'table') end), group 'make_sections' { test('sanity check', function () local blks = { pandoc.Header(1, {pandoc.Str 'First'}), pandoc.Header(2, {pandoc.Str 'Second'}), pandoc.Header(2, {pandoc.Str 'Third'}), } local opts = PANDOC_WRITER_OPTIONS local hblks = structure.make_sections(blks, opts) assert.are_equal('Div', hblks[1].t) assert.are_equal('Header', hblks[1].content[1].t) end), test('respects number_sections', function () local blks = { pandoc.Header(1, {pandoc.Str 'First'}), pandoc.Para 'Vestibulum convallis, lorem a tempus semper.' } local hblks = structure.make_sections(blks, {number_sections = true}) assert.are_equal('Div', hblks[1].t) assert.are_equal('Header', hblks[1].content[1].t) assert.are_equal('1', hblks[1].content[1].attributes['number']) end), test('respects base_level', function () local blks = { pandoc.Header(1, {pandoc.Str 'First'}), pandoc.Para 'Curabitur lacinia pulvinar nibh.', pandoc.Header(3, {pandoc.Str 'First'}), -- Skipping level 2 } local opts = { number_sections = true, base_level = 1 } local hblks = structure.make_sections(blks, opts) assert.are_equal('Div', hblks[1].t) assert.are_equal('Header', hblks[1].content[1].t) assert.are_equal('1', hblks[1].content[1].attributes['number']) assert.are_equal('1.0.1', hblks[1].content[3].attributes['number']) end) }, group 'split_into_chunks' { test('is function', function () assert.are_equal(type(structure.split_into_chunks), 'function') end), test('returns a chunked doc', function () assert.are_equal( pandoc.utils.type(structure.split_into_chunks(pandoc.Pandoc{})), 'ChunkedDoc' ) end), }, group 'table_of_contents' { test('is function', function () assert.are_equal(type(structure.table_of_contents), 'function') end), test('returns a bullet list', function () assert.are_equal( pandoc.utils.type(structure.table_of_contents{}), 'Block' ) assert.are_equal( structure.table_of_contents{}.t, 'BulletList' ) end), test('returns a toc for a list of blocks', function () local body = pandoc.Blocks{ pandoc.Header(1, 'First'), pandoc.Para('A sentence placed below the first structure.'), pandoc.Header(2, 'Subsection'), pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'), pandoc.Header(1, 'Second'), pandoc.Para('Integer placerat tristique nisl.') } assert.are_equal( structure.table_of_contents(body), pandoc.BulletList{ {pandoc.Plain('First'), pandoc.BulletList{{pandoc.Plain 'Subsection'}} }, {pandoc.Plain('Second')} } ) end), test('returns a toc for a chunked doc', function () local doc = pandoc.Pandoc { pandoc.Header(1, 'First', {id='first'}), pandoc.Para('A sentence placed below the first structure.'), pandoc.Header(2, 'Subsection', {id='subsection'}), pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'), pandoc.Header(1, 'Second', {id='second'}), pandoc.Para('Integer placerat tristique nisl.') } local chunked = structure.split_into_chunks(doc, {chunk_level = 2}) assert.are_equal( structure.table_of_contents(chunked), pandoc.BulletList{ {pandoc.Plain({pandoc.Link('First', 'chunk-001#first', '', {id='toc-first'})}), pandoc.BulletList{{pandoc.Plain({pandoc.Link('Subsection', 'chunk-002#subsection', '', {id='toc-subsection'})})}} }, {pandoc.Plain({pandoc.Link('Second', 'chunk-003#second', '', {id='toc-second'})})} } ) end), test('respects toc-depth option', function () local doc = pandoc.Pandoc { pandoc.Header(1, 'First', {id='first'}), pandoc.Para('A sentence placed below the first structure.'), pandoc.Header(2, 'Subsection', {id='subsection'}), pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'), pandoc.Header(1, 'Second', {id='second'}), pandoc.Para('Integer placerat tristique nisl.') } local chunked = structure.split_into_chunks(doc) assert.are_equal( structure.table_of_contents(chunked, {toc_depth = 1}), pandoc.BulletList{ {pandoc.Plain({pandoc.Link('First', 'chunk-001#first', '', {id='toc-first'})})}, {pandoc.Plain({pandoc.Link('Second', 'chunk-002#second', '', {id='toc-second'})})} } ) end), }, group 'unique_identifier' { test('returns an identifier based on the input', function () local inlines = pandoc.Inlines{pandoc.Emph{'This'}, ' is nice'} local id = structure.unique_identifier(inlines) assert.are_equal('this-is-nice', id) end), test('respects the list of used IDs', function () local inlines = pandoc.Inlines('Hello, World!') local used = {['hello-world'] = true} local id = structure.unique_identifier(inlines, used) assert.are_equal('hello-world-1', id) end), test('defaults to pandoc Markdown identifiers', function () local inlines = pandoc.Inlines('Mr. Jones') local id = structure.unique_identifier(inlines, {}) assert.are_equal('mr.-jones', id) end), test('can generate gfm identifiers', function () local inlines = pandoc.Inlines('Mr. Jones') local exts = {'gfm_auto_identifiers'} local id = structure.unique_identifier(inlines, {}, exts) assert.are_equal('mr-jones', id) end), } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-template.lua 0000644 0000000 0000000 00000006405 07346545000 022006 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local pandoc = require 'pandoc' 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 'get' { test('is function', function () assert.are_equal(type(template.get), 'function') end), test('searches the template data directory', function () assert.are_equal( template.default 'html5', template.get 'default.html5' ) end), test('fails on non-existent file', function () local success, msg = pcall(function () return pandoc.utils.type(template.get 'nosuchfile.nope') 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$')), '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)), 'Template' ) end), test('fails if template has non-existing partial', function () assert.error_matches( function () return template.compile('${ nosuchpartial() }') end, 'Could not find data file' ) 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)), 'Template' ) end), }, group 'apply' { test('is function', function () assert.are_equal(type(template.apply), 'function') end), test('returns a Doc value', function () local tmpl = template.compile('placeholder') assert.are_equal( pandoc.utils.type(template.apply(tmpl, {})), 'Doc' ) end), test('applies the given context', function () local tmpl = template.compile('song: $title$') local context = {title = 'Along Comes Mary'} assert.are_equal( template.apply(tmpl, context):render(), 'song: Along Comes Mary' ) end), test('accepts string as template', function () local context = {number = '2'} assert.are_equal( template.apply('Song $number$', context):render(), 'Song 2' ) end) }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-text.lua 0000644 0000000 0000000 00000004230 07346545000 021151 0 ustar 00 0000000 0000000 -- -- Tests for the pandoc.text module -- local text = require 'pandoc.text' local tasty = require 'tasty' local group = tasty.test_group local test = tasty.test_case local assert = tasty.assert assert.is_function = function (x) assert.are_equal(type(x), 'function') end -- We rely mostly on the tests in the `hslua-module-text` module. The -- only thing we need to test is whether `pandoc.text` is available, -- whether all functions are defined, and whether `require 'text'` works -- (for backwards compatibility). return { group 'module' { test('is table', function () assert.are_equal(type(text), 'table') end), test('can be required as "text"', function () assert.are_equal(require 'text', require 'pandoc.text') end) }, group 'functions' { test('fromencoding', function () assert.is_function(text.fromencoding) end), test('len', function () assert.is_function(text.len) end), test('lower', function () assert.is_function(text.lower) end), test('reverse', function () assert.is_function(text.reverse) end), test('sub', function () assert.is_function(text.sub) end), group 'subscript' { test('is a function', function () assert.is_function(text.subscript) end), test('converts a string to Unicode subscript chars', function () assert.are_equal(text.subscript '1+(9-7)', '₁₊₍₉₋₇₎') end), test('returns nil if the input contains unsupported chars', function () assert.is_nil(text.subscript '00ä') end), }, group 'superscript' { test('is a function', function () assert.is_function(text.superscript) end), test('converts a string to Unicode superscript chars', function () assert.are_equal(text.superscript '1+(9-7)', '¹⁺⁽⁹⁻⁷⁾') end), test('returns nil if the input contains unsupported chars', function () assert.is_nil(text.superscript '00ä') end), }, test('toencoding', function () assert.is_function(text.toencoding) end), test('upper', function () assert.is_function(text.upper) end), }, } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-types.lua 0000644 0000000 0000000 00000005656 07346545000 021346 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local types = require 'pandoc.types' local Version = types.Version local assert = tasty.assert local test = tasty.test_case local group = tasty.test_group return { group 'Version' { group 'constructor' { test('has type `userdata`', function () assert.are_same(type(Version {2}), 'userdata') end), test('accepts list of integers', function () assert.are_same(type(Version {2, 7, 3}), 'userdata') end), test('accepts a single integer', function () assert.are_same(Version(5), Version {5}) end), test('accepts version as string', function () assert.are_same( Version '4.45.1', Version {4, 45, 1} ) end), test('non-version string is rejected', function () local success, msg = pcall(function () Version '11friends' end) assert.is_falsy(success) assert.is_truthy(tostring(msg):match('11friends')) end) }, group 'comparison' { test('smaller (equal) than', function () assert.is_truthy(Version {2, 58, 3} < Version {2, 58, 4}) assert.is_falsy(Version {2, 60, 1} < Version {2, 59, 2}) assert.is_truthy(Version {0, 14, 3} < Version {0, 14, 3, 1}) assert.is_truthy(Version {3, 58, 3} <= Version {4}) assert.is_truthy(Version {0, 14, 3} <= Version {0, 14, 3, 1}) end), test('larger (equal) than', function () assert.is_truthy(Version{2,58,3} > Version {2, 57, 4}) assert.is_truthy(Version{2,58,3} > Version {2, 58, 2}) assert.is_truthy(Version {0, 8} >= Version {0, 8}) assert.is_falsy(Version {0, 8} >= Version {0, 8, 2}) end), test('equality', function () assert.is_truthy(Version '8.8', Version {8, 8}) end), test('second argument can be a version string', function () assert.is_truthy(Version '8' < '9.1') assert.is_falsy(Version '8.8' < '8.7') end), }, group 'conversion to string' { test('converting from and to string is a noop', function () local version_string = '1.19.4' assert.are_equal(tostring(Version(version_string)), version_string) end) }, group 'convenience functions' { test('throws error if version is too old', function () local actual = Version {2, 8} local expected = Version {2, 9} assert.error_matches( function () actual:must_be_at_least(expected) end, 'expected version 2.9 or newer, got 2.8' ) end), test('does nothing if expected version is older than actual', function () local actual = Version '2.9' local expected = Version '2.8' actual:must_be_at_least(expected) end), test('does nothing if expected version equals to actual', function () local actual = Version '2.8' local expected = Version '2.8' actual:must_be_at_least(expected) end) } } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc-utils.lua 0000644 0000000 0000000 00000030625 07346545000 021334 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local pandoc = require 'pandoc' local utils = require 'pandoc.utils' local io = require 'io' local assert = tasty.assert local test = tasty.test_case local group = tasty.test_group return { group 'blocks_to_inlines' { test('default separator', function () local blocks = { pandoc.Para { pandoc.Str 'Paragraph1' }, pandoc.Para { pandoc.Emph { pandoc.Str 'Paragraph2' } } } local expected = { pandoc.Str 'Paragraph1', pandoc.LineBreak(), pandoc.Emph { pandoc.Str 'Paragraph2' } } assert.are_same( expected, utils.blocks_to_inlines(blocks) ) end), test('custom separator', function () local blocks = { pandoc.Para{ pandoc.Str 'Paragraph1' }, pandoc.Para{ pandoc.Emph 'Paragraph2' } } local expected = { pandoc.Str 'Paragraph1', pandoc.LineBreak(), pandoc.Emph { pandoc.Str 'Paragraph2' } } assert.are_same( expected, utils.blocks_to_inlines(blocks, { pandoc.LineBreak() }) ) end) }, group 'equals' { test('compares Pandoc elements', function () assert.is_truthy( utils.equals(pandoc.Pandoc{'foo'}, pandoc.Pandoc{'foo'}) ) end), test('compares Block elements', function () assert.is_truthy( utils.equals(pandoc.Plain{'foo'}, pandoc.Plain{'foo'}) ) assert.is_falsy( utils.equals(pandoc.Para{'foo'}, pandoc.Plain{'foo'}) ) end), test('compares Inline elements', function () assert.is_truthy( utils.equals(pandoc.Emph{'foo'}, pandoc.Emph{'foo'}) ) assert.is_falsy( utils.equals(pandoc.Emph{'foo'}, pandoc.Strong{'foo'}) ) end), test('compares Inline with Block elements', function () assert.is_falsy( utils.equals(pandoc.Emph{'foo'}, pandoc.Plain{'foo'}) ) assert.is_falsy( utils.equals(pandoc.Para{'foo'}, pandoc.Strong{'foo'}) ) end), test('compares Pandoc with Block elements', function () assert.is_falsy( utils.equals(pandoc.Pandoc{'foo'}, pandoc.Plain{'foo'}) ) assert.is_falsy( utils.equals(pandoc.Para{'foo'}, pandoc.Pandoc{'foo'}) ) end), }, group 'make_sections' { test('sanity check', function () local blks = { pandoc.Header(1, {pandoc.Str 'First'}), pandoc.Header(2, {pandoc.Str 'Second'}), pandoc.Header(2, {pandoc.Str 'Third'}), } local hblks = utils.make_sections(true, 1, blks) assert.are_equal('Div', hblks[1].t) assert.are_equal('Header', hblks[1].content[1].t) assert.are_equal('1', hblks[1].content[1].attributes['number']) end) }, group 'normalize_date' { test('09 Nov 1989', function () assert.are_equal('1989-11-09', utils.normalize_date '09 Nov 1989') end), test('12/31/2017', function () assert.are_equal('2017-12-31', utils.normalize_date '12/31/2017') end), }, group 'references' { test('gets references from doc', function () local ref = { ['author'] = { {given = 'Max', family = 'Mustermann'} }, ['container-title'] = pandoc.Inlines('JOSS'), ['id'] = 'test', ['issued'] = {['date-parts'] = {{2021}}}, ['title'] = pandoc.Inlines{ pandoc.Quoted('DoubleQuote', 'Interesting'), pandoc.Space(), 'work' }, ['type'] = 'article-journal', } local nocite = pandoc.Cite( '@test', {pandoc.Citation('test', 'NormalCitation')} ) local doc = pandoc.Pandoc({}, {nocite = nocite, references = {ref}}) assert.are_same({ref}, pandoc.utils.references(doc)) end) }, group 'run_lua_filter' { test('runs a filter', function () local doc = pandoc.Pandoc("indivisible words") pandoc.system.with_temporary_directory('lua-filter', function (dir) local filter_path = pandoc.path.join{dir, 'test.lua'} local filter = 'function Space() return " " end' local fh = io.open(filter_path, 'wb') fh:write(filter) fh:close() assert.are_equal( utils.run_lua_filter(doc, filter_path), pandoc.Pandoc( pandoc.Plain(pandoc.Inlines{"indivisible", " ", "words"}) ) ) end) end), test("doesn't change the local environment by default", function () pandoc.system.with_temporary_directory('lua-filter', function (dir) local filter_path = pandoc.path.join{dir, 'test.lua'} local foo local filter = 'foo = 42' local fh = io.open(filter_path, 'wb') fh:write(filter) fh:close() utils.run_lua_filter(pandoc.Pandoc{}, filter_path) assert.is_nil(foo) end) end), test("accepts an environment in which the filter is executed", function () pandoc.system.with_temporary_directory('lua-filter', function (dir) local filter_path = pandoc.path.join{dir, 'test.lua'} local filter = 'foo = 42' local fh = io.open(filter_path, 'wb') fh:write(filter) fh:close() utils.run_lua_filter(pandoc.Pandoc{}, filter_path, _ENV) assert.are_equal(_ENV.foo, 42) end) end) }, group 'sha1' { test('hashing', function () local ref_hash = '0a0a9f2a6772942557ab5355d76af442f8f65e01' assert.are_equal(ref_hash, utils.sha1 'Hello, World!') end) }, group 'stringify' { test('Inline', function () local inline = pandoc.Emph{ pandoc.Str 'Cogito', pandoc.Space(), pandoc.Str 'ergo', pandoc.Space(), pandoc.Str 'sum.', } assert.are_equal('Cogito ergo sum.', utils.stringify(inline)) end), test('Block', function () local block = pandoc.Para{ pandoc.Str 'Make', pandoc.Space(), pandoc.Str 'it', pandoc.Space(), pandoc.Str 'so.', } assert.are_equal('Make it so.', utils.stringify(block)) end), test('boolean', function () assert.are_equal('true', utils.stringify(true)) assert.are_equal('false', utils.stringify(false)) end), test('number', function () assert.are_equal('5', utils.stringify(5)) assert.are_equal('23.23', utils.stringify(23.23)) end), test('Attr', function () local attr = pandoc.Attr('foo', {'bar'}, {a = 'b'}) assert.are_equal('', utils.stringify(attr)) end), test('List', function () local list = pandoc.List{pandoc.Str 'a', pandoc.Blocks('b')} assert.are_equal('ab', utils.stringify(list)) end), test('Blocks', function () local blocks = pandoc.Blocks{pandoc.Para 'a', pandoc.Header(1, 'b')} assert.are_equal('ab', utils.stringify(blocks)) end), test('Inlines', function () local inlines = pandoc.Inlines{pandoc.Str 'a', pandoc.Subscript('b')} assert.are_equal('ab', utils.stringify(inlines)) end), test('Caption', function () local capt = pandoc.Caption(pandoc.Para{pandoc.Str 'a', pandoc.Emph('b')}) assert.are_equal('ab', utils.stringify(capt)) end), test('Cell', function () local cell = pandoc.Cell(pandoc.Para{pandoc.Str 'a', pandoc.Emph('b')}) assert.are_equal('ab', utils.stringify(cell)) end), test('TableFoot', function () local tf = pandoc.TableFoot{pandoc.Row{pandoc.Cell{pandoc.Plain "x y"}}} assert.are_equal('x y', utils.stringify(tf)) end), test('TableHead', function () local th = pandoc.TableHead{pandoc.Row{pandoc.Cell{pandoc.Plain "head1"}}} assert.are_equal('head1', utils.stringify(th)) end), test('Meta', function () local meta = pandoc.Meta{ a = pandoc.Inlines 'funny and ', b = 'good movie', c = pandoc.List{pandoc.Inlines{pandoc.Str '!'}} } assert.are_equal('funny and good movie!', utils.stringify(meta)) end), }, group 'to_roman_numeral' { test('convertes number', function () assert.are_equal('MDCCCLXXXVIII', utils.to_roman_numeral(1888)) end), test('fails on non-convertible argument', function () assert.is_falsy(pcall(utils.to_roman_numeral, 'not a number')) end) }, group 'type' { test('nil', function () assert.are_equal(utils.type(nil), 'nil') end), test('boolean', function () assert.are_equal(utils.type(true), 'boolean') assert.are_equal(utils.type(false), 'boolean') end), test('number', function () assert.are_equal(utils.type(5), 'number') assert.are_equal(utils.type(-3.02), 'number') end), test('string', function () assert.are_equal(utils.type(''), 'string') assert.are_equal(utils.type('asdf'), 'string') end), test('plain table', function () assert.are_equal(utils.type({}), 'table') end), test('List', function () assert.are_equal(utils.type(pandoc.List{}), 'List') end), test('Inline', function () assert.are_equal(utils.type(pandoc.Str 'a'), 'Inline') assert.are_equal(utils.type(pandoc.Emph 'emphasized'), 'Inline') end), test('Inlines', function () assert.are_equal(utils.type(pandoc.Inlines{pandoc.Str 'a'}), 'Inlines') assert.are_equal(utils.type(pandoc.Inlines{pandoc.Emph 'b'}), 'Inlines') end), test('Blocks', function () assert.are_equal(utils.type(pandoc.Para 'a'), 'Block') assert.are_equal(utils.type(pandoc.CodeBlock 'true'), 'Block') end), test('Inlines', function () assert.are_equal(utils.type(pandoc.Blocks{'a'}), 'Blocks') assert.are_equal(utils.type(pandoc.Blocks{pandoc.CodeBlock 'b'}), 'Blocks') end), }, group 'to_simple_table' { test('convertes Table', function () function simple_cell (blocks) return { attr = pandoc.Attr(), alignment = "AlignDefault", contents = blocks, col_span = 1, row_span = 1, } end local tbl = pandoc.Table( {long = {pandoc.Plain { pandoc.Str "the", pandoc.Space(), pandoc.Str "caption"}}}, {{pandoc.AlignDefault, nil}}, pandoc.TableHead{pandoc.Row{simple_cell{pandoc.Plain "head1"}}}, {{ attr = pandoc.Attr(), body = {pandoc.Row{simple_cell{pandoc.Plain "cell1"}}}, head = {}, row_head_columns = 0 }}, pandoc.TableFoot(), pandoc.Attr() ) local stbl = utils.to_simple_table(tbl) assert.are_equal('SimpleTable', stbl.t) assert.are_equal('head1', utils.stringify(stbl.headers[1])) assert.are_equal('cell1', utils.stringify(stbl.rows[1][1])) assert.are_equal('the caption', utils.stringify(pandoc.Span(stbl.caption))) end), test('fails on para', function () assert.is_falsy(pcall(utils.to_simple_table, pandoc.Para "nope")) end), }, group 'from_simple_table' { test('converts SimpleTable to Table', function () local caption = {pandoc.Str "Overview"} local aligns = {pandoc.AlignDefault, pandoc.AlignDefault} local widths = {0, 0} -- let pandoc determine col widths local headers = { {pandoc.Plain "Language"}, {pandoc.Plain "Typing"} } local rows = { {{pandoc.Plain "Haskell"}, {pandoc.Plain "static"}}, {{pandoc.Plain "Lua"}, {pandoc.Plain "Dynamic"}}, } local simple_table = pandoc.SimpleTable( caption, aligns, widths, headers, rows ) local tbl = utils.from_simple_table(simple_table) assert.are_equal("Table", tbl.t) assert.are_same( {pandoc.Plain(caption)}, tbl.caption.long ) -- reversible assert.are_same(simple_table, utils.to_simple_table(tbl)) end), test('empty caption', function () local simple_table = pandoc.SimpleTable( {}, {pandoc.AlignDefault}, {0}, {{pandoc.Plain 'a'}}, {{{pandoc.Plain 'b'}}} ) local tbl = utils.from_simple_table(simple_table) assert.are_equal( pandoc.Blocks{}, tbl.caption.long ) assert.is_nil(tbl.caption.short) end), test('empty body', function () local simple_table = pandoc.SimpleTable( pandoc.Inlines('a nice caption'), {pandoc.AlignDefault}, {0}, {{pandoc.Plain 'a'}}, {} ) local tbl = utils.from_simple_table(simple_table) tbl.bodies:map(print) assert.are_same(pandoc.List(), tbl.bodies) end), } } pandoc-lua-engine-0.5.1/test/lua/module/pandoc.lua 0000644 0000000 0000000 00000045542 07346545000 020202 0 ustar 00 0000000 0000000 local tasty = require 'tasty' local test = tasty.test_case local group = tasty.test_group local assert = tasty.assert function os_is_windows () return package.config:sub(1,1) == '\\' end -- Constructor behavior is tested in the hslua-pandoc-types module, so -- we just make sure the functions are present. return { group 'Constructors' { group 'Misc' { test('pandoc.Attr is a function', function () assert.are_equal(type(pandoc.Attr), 'function') end), test('pandoc.AttributeList is a function', function () assert.are_equal(type(pandoc.AttributeList), 'function') end), test('pandoc.Blocks is a function', function () assert.are_equal(type(pandoc.Blocks), 'function') end), test('pandoc.Citation is a function', function () assert.are_equal(type(pandoc.Citation), 'function') end), test('pandoc.Inlines is a function', function () assert.are_equal(type(pandoc.Inlines), 'function') end), test('pandoc.SimpleTable is a function', function () assert.are_equal(type(pandoc.SimpleTable), 'function') end), test('pandoc.Meta is a function', function () assert.are_equal(type(pandoc.Meta), 'function') end), test('pandoc.Pandoc is a function', function () assert.are_equal(type(pandoc.Pandoc), 'function') end), }, group "Inline elements" { test('pandoc.AttributeList is a function', function () assert.are_equal(type(pandoc.Cite), 'function') end), test('pandoc.AttributeList is a function', function () assert.are_equal(type(pandoc.Code), 'function') end), test('pandoc.Emph is a function', function () assert.are_equal(type(pandoc.Emph), 'function') end), test('pandoc.Image is a function', function () assert.are_equal(type(pandoc.Image), 'function') end), test('pandoc.Link is a function', function () assert.are_equal(type(pandoc.Link), 'function') end), test('pandoc.Math is a function', function () assert.are_equal(type(pandoc.Math), 'function') end), test('pandoc.Note is a function', function () assert.are_equal(type(pandoc.Note), 'function') end), test('pandoc.Quoted is a function', function () assert.are_equal(type(pandoc.Quoted), 'function') end), test('pandoc.SmallCaps is a function', function () assert.are_equal(type(pandoc.SmallCaps), 'function') end), test('pandoc.SoftBreak is a function', function () assert.are_equal(type(pandoc.SoftBreak), 'function') end), test('pandoc.Span is a function', function () assert.are_equal(type(pandoc.Span), 'function') end), test('pandoc.Str is a function', function () assert.are_equal(type(pandoc.Str), 'function') end), test('pandoc.Strikeout is a function', function () assert.are_equal(type(pandoc.Strikeout), 'function') end), test('pandoc.Strong is a function', function () assert.are_equal(type(pandoc.Strong), 'function') end), test('pandoc.Subscript is a function', function () assert.are_equal(type(pandoc.Subscript), 'function') end), test('pandoc.Superscript is a function', function () assert.are_equal(type(pandoc.Superscript), 'function') end), test('pandoc.Underline is a function', function () assert.are_equal(type(pandoc.Underline), 'function') end), }, group "Block elements" { test('pandoc.BlockQuote is a function', function () assert.are_equal(type(pandoc.BlockQuote), 'function') end), test('pandoc.BulletList is a function', function () assert.are_equal(type(pandoc.BulletList), 'function') end), test('pandoc.CodeBlock is a function', function () assert.are_equal(type(pandoc.CodeBlock), 'function') end), test('pandoc.DefinitionList is a function', function () assert.are_equal(type(pandoc.DefinitionList), 'function') end), test('pandoc.Div is a function', function () assert.are_equal(type(pandoc.Div), 'function') end), test('pandoc.Header is a function', function () assert.are_equal(type(pandoc.Header), 'function') end), test('pandoc.LineBlock is a function', function () assert.are_equal(type(pandoc.LineBlock), 'function') end), test('pandoc.Figure is a function', function () assert.are_equal(type(pandoc.Figure), 'function') end), test('pandoc.OrderedList is a function', function () assert.are_equal(type(pandoc.OrderedList), 'function') end), test('pandoc.Para is a function', function () assert.are_equal(type(pandoc.Para), 'function') end), test('pandoc.Plain is a function', function () assert.are_equal(type(pandoc.Plain), 'function') end), test('pandoc.RawBlock is a function', function () assert.are_equal(type(pandoc.Plain), 'function') end), test('pandoc.Table is a function', function () assert.are_equal(type(pandoc.Table), 'function') end), } }, group 'MetaValue elements' { test('MetaList elements behave like lists', function () local metalist = pandoc.MetaList{} assert.are_equal(type(metalist.insert), 'function') assert.are_equal(type(metalist.remove), 'function') end), test('`tag` is an alias for `t``', function () assert.are_equal((pandoc.MetaList{}).tag, (pandoc.MetaList{}).t) assert.are_equal((pandoc.MetaMap{}).tag, (pandoc.MetaMap{}).t) assert.are_equal((pandoc.MetaInlines{}).tag, (pandoc.MetaInlines{}).t) assert.are_equal((pandoc.MetaBlocks{}).tag, (pandoc.MetaBlocks{}).t) end), }, group 'Meta' { test('inline list is treated as MetaInlines', function () local meta = pandoc.Pandoc({}, {test = {pandoc.Emph 'check'}}).meta assert.are_same(meta.test, {pandoc.Emph{pandoc.Str 'check'}}) end), test('inline element is treated as MetaInlines singleton', function () local meta = pandoc.Pandoc({}, {test = pandoc.Emph 'check'}).meta assert.are_same(meta.test, {pandoc.Emph{pandoc.Str 'check'}}) end), test('block list is treated as MetaBlocks', function () local meta = pandoc.Pandoc({}, {test = {pandoc.Plain 'check'}}).meta assert.are_same(meta.test, {pandoc.Plain{pandoc.Str 'check'}}) end), test('block element is treated as MetaBlocks singleton', function () local meta = pandoc.Pandoc({}, {test = pandoc.Plain 'check'}).meta assert.are_same(meta.test, {pandoc.Plain{pandoc.Str 'check'}}) end), }, group 'Pandoc' { test('normalize', function () local doc = pandoc.Pandoc({{'a', pandoc.Space(), pandoc.Space(), 'b'}}) local normalized = pandoc.Pandoc({{'a', pandoc.Space(), 'b'}}) assert.are_equal(normalized, doc:normalize()) end), }, group 'Other types' { group 'ReaderOptions' { test('returns a userdata value', function () local opts = pandoc.ReaderOptions {} assert.are_equal(type(opts), 'userdata') end), test('can construct from table', function () local opts = pandoc.ReaderOptions {columns = 66} assert.are_equal(opts.columns, 66) end), test('can construct from other ReaderOptions value', function () local orig = pandoc.ReaderOptions{columns = 65} local copy = pandoc.ReaderOptions(orig) for k, v in pairs(orig) do assert.are_same(copy[k], v) end assert.are_equal(copy.columns, 65) end), }, }, group 'clone' { test('clones Attr', function () local attr = pandoc.Attr('test', {'my-class'}, {foo = 'bar'}) local cloned = attr:clone() attr.identifier = '' attr.classes = {} attr.attributes = {} assert.are_same(cloned.identifier, 'test') assert.are_same(cloned.classes, {'my-class'}) assert.are_same(cloned.attributes.foo, 'bar') end), test('clones ListAttributes', function () local la = pandoc.ListAttributes(2, pandoc.DefaultStyle, pandoc.Period) local cloned = la:clone() la.start = 9 assert.are_same(cloned.start, 2) end), test('clones Para', function () local para = pandoc.Para {pandoc.Str 'Hello'} local cloned = para:clone() para.content[1].text = 'bye' assert.are_same(cloned, pandoc.Para {pandoc.Str 'Hello'}) end), test('clones Str', function () local str = pandoc.Str 'Hello' local cloned = str:clone() str.text = 'bye' assert.are_same(cloned.text, 'Hello') end), test('clones Citation', function () local cite = pandoc.Citation('leibniz', pandoc.AuthorInText) local cloned = cite:clone() cite.id = 'newton' assert.are_same(cloned.id, 'leibniz') assert.are_same(cite.id, 'newton') assert.are_same(cite.mode, cloned.mode) end), }, group 'pipe' { test('external string processing', function () if os_is_windows() then local pipe_result = pandoc.pipe('find', {'hi'}, 'hi') assert.are_equal('hi', pipe_result:match '%a+') else local pipe_result = pandoc.pipe('tr', {'a', 'b'}, 'abc') assert.are_equal('bbc', pipe_result:match '%a+') end end), test('failing pipe', function () if os_is_windows() then local success, err = pcall(pandoc.pipe, 'find', {'/a'}, 'hi') assert.is_falsy(success) assert.are_equal('find', err.command) assert.is_truthy(err.error_code ~= 0) else local success, err = pcall(pandoc.pipe, 'false', {}, 'abc') assert.is_falsy(success) assert.are_equal('false', err.command) assert.are_equal(1, err.error_code) assert.are_equal('', err.output) end end) }, group 'read' { test('Markdown', function () local valid_markdown = '*Hello*, World!\n' local expected = pandoc.Pandoc({ pandoc.Para { pandoc.Emph { pandoc.Str 'Hello' }, pandoc.Str ',', pandoc.Space(), pandoc.Str 'World!' } }) assert.are_same(expected, pandoc.read(valid_markdown)) end), test('unsupported extension', function () assert.error_matches( function () pandoc.read('foo', 'gfm+empty_paragraphs') end, 'The extension empty_paragraphs is not supported for gfm' ) end), test('read with other indented code classes', function() local indented_code = ' return true' local expected = pandoc.Pandoc({ pandoc.CodeBlock('return true', {class='foo'}) }) assert.are_same( expected, pandoc.read(indented_code, 'markdown', {indented_code_classes={'foo'}}) ) end), test('can read epub', function () local epub = io.open('lua/module/tiny.epub', 'rb') local blocks = pandoc.read(epub:read'a', 'epub').blocks assert.are_equal( blocks[#blocks], pandoc.Para { pandoc.Emph 'EPUB' } ) end), test('failing read', function () assert.error_matches( function () pandoc.read('foo', 'nosuchreader') end, 'Unknown input format nosuchreader' ) end), group 'read_env' { test('images are added to the mediabag', function () local epub = io.open('lua/module/sample.epub', 'rb'):read('a') local _ = pandoc.read(epub, 'epub') assert.are_equal(#pandoc.mediabag.list(), 1) end), test('images from EPUB are added when using the sandbox', function () local epub = io.open('lua/module/sample.epub', 'rb'):read('a') local _ = pandoc.read(epub, 'epub', nil, {}) assert.are_equal(#pandoc.mediabag.list(), 1) end), test('includes work in global env', function () local tex = '\\include{lua/module/include.tex}' local doc = pandoc.read(tex, 'latex') assert.are_equal( doc.blocks, pandoc.Blocks{pandoc.Para 'included'} ) end), test('sandbox disallows access to the filesystem', function () local tex = '\\include{lua/module/include.tex}' local doc = pandoc.read(tex, 'latex', nil, {}) assert.are_equal(doc.blocks, pandoc.Blocks{}) end), test('files can be added to the sandbox', function () local tex = '\\include{lua/module/include.tex}' local doc = pandoc.read(tex, 'latex', nil, {'lua/module/include.tex'}) assert.are_equal( doc.blocks, pandoc.Blocks{pandoc.Para 'included'} ) end), test('sandbox files can be given as key-value pairs', function () local tex = '\\include{lua/module/include.tex}' local files = { ['lua/module/include.tex'] = 'Hello' } local doc = pandoc.read(tex, 'latex', nil, files) assert.are_equal( doc.blocks, pandoc.Blocks{pandoc.Para 'Hello'} ) end), test('kv-pairs override contents read from file system', function () local tex = '\\include{lua/module/include.tex}' local files = { 'lua/module/include.tex', ['lua/module/include.tex'] = 'Hello' } local doc = pandoc.read(tex, 'latex', nil, files) assert.are_equal( doc.blocks, pandoc.Blocks{pandoc.Para 'Hello'} ) end), }, group 'extensions' { test('string spec', function () local doc = pandoc.read('"vice versa"', 'markdown-smart') assert.are_equal(doc, pandoc.Pandoc{pandoc.Para '"vice versa"'}) end), test('unsupported extension', function () assert.error_matches( function () pandoc.read('foo', 'gfm+empty_paragraphs') end, 'The extension empty_paragraphs is not supported for gfm' ) end), test('unknown extension', function () local format_spec = { format = 'markdown', extensions = {'nope'}} assert.error_matches( function () pandoc.read('x', format_spec) end, 'The extension nope is not supported for markdown' ) end), test('fails on invalid extension', function () local format_spec = { format = 'markdown', extensions = {'nope'}} assert.error_matches( function () pandoc.read('nu-uh', format_spec) end, 'The extension nope is not supported for markdown' ) end), }, }, group 'walk_block' { test('block walking order', function () local acc = {} local nested_nums = pandoc.Div { pandoc.Para{pandoc.Str'1'}, pandoc.Div{ pandoc.Para{pandoc.Str'2'}, pandoc.Para{pandoc.Str'3'} }, pandoc.Para{pandoc.Str'4'} } pandoc.walk_block( nested_nums, {Para = function (p) table.insert(acc, p.content[1].text) end} ) assert.are_equal('1234', table.concat(acc)) end) }, group 'walk_inline' { test('inline walking order', function () local acc = {} local nested_nums = pandoc.Span { pandoc.Str'1', pandoc.Emph { pandoc.Str'2', pandoc.Str'3' }, pandoc.Str'4' } pandoc.walk_inline( nested_nums, {Str = function (s) table.insert(acc, s.text) end} ) assert.are_equal('1234', table.concat(acc)) end) }, group 'write' { test('string spec', function () local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')} local plain = pandoc.write(doc, 'plain+smart') assert.are_equal(plain, '"vice versa"\n') end), test('table format spec with extensions list', function () local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')} local format_spec = { format = 'plain', extensions = {'smart'}} local plain = pandoc.write(doc, format_spec) assert.are_equal(plain, '"vice versa"\n') end), test('table format spec with `enable`/`disable` diff', function () local diff = { enable = {'smart'} } local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')} local format_spec = { format = 'plain', extensions = diff} local plain = pandoc.write(doc, format_spec) assert.are_equal(plain, '"vice versa"\n') end), test('table format spec with set-like diff', function () local diff = { smart = true, auto_identifiers = false } local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')} local format_spec = { format = 'plain', extensions = diff} local plain = pandoc.write(doc, format_spec) assert.are_equal(plain, '"vice versa"\n') end), test('fails on invalid extension', function () local doc = pandoc.Pandoc{'nope'} local format_spec = { format = 'plain', extensions = {'nope'}} assert.error_matches( function () pandoc.write(doc, format_spec) end, 'The extension nope is not supported for plain' ) end), }, group 'with_state' { test('request_headers can be modified', function () local headers = { {"Authorization", "Basic my-secret"} } pandoc.with_state({request_headers = headers}, function () assert.are_same(PANDOC_STATE.request_headers, headers) end) end), test('resource_path can be modified', function () local paths = {'.', '/test/resource/path' } pandoc.with_state({resource_path = paths}, function () assert.are_same(PANDOC_STATE.resource_path, paths) end) end), test('user_data_dir can be modified', function () local opts = {user_data_dir = '/my/test/path'} pandoc.with_state(opts, function () assert.are_equal(PANDOC_STATE.user_data_dir, '/my/test/path') end) end), test('original value is restored afterwards', function () local orig_user_data_dir = PANDOC_STATE.user_data_dir local opts = {user_data_dir = '/my/test/path'} pandoc.with_state(opts, function () end) assert.are_equal(PANDOC_STATE.user_data_dir, orig_user_data_dir) end), test('unsupported options trigger an error', function () local orig_log = PANDOC_STATE.log local opts = {log = 'nonsense'} assert.error_matches( function () pandoc.with_state(opts, function () assert.are_same(PANDOC_STATE.log, orig_log) end) end, "Unknown or unsupported" ) assert.are_same(PANDOC_STATE.log, orig_log) end), }, group 'Marshal' { group 'Inlines' { test('Strings are broken into words', function () assert.are_equal( pandoc.Emph 'Nice, init?', pandoc.Emph{pandoc.Str 'Nice,', pandoc.Space(), pandoc.Str 'init?'} ) end) }, group 'Blocks' { test('Strings are broken into words and wrapped in Plain', function () assert.are_equal( pandoc.Div{ pandoc.Plain{pandoc.Str 'Nice,', pandoc.Space(), pandoc.Str 'init?'} }, pandoc.Div{'Nice, init?'} ) end) } } } pandoc-lua-engine-0.5.1/test/lua/module/partial.test 0000644 0000000 0000000 00000000000 07346545000 020544 0 ustar 00 0000000 0000000 pandoc-lua-engine-0.5.1/test/lua/module/sample.epub 0000644 0000000 0000000 00000012622 07346545000 020362 0 ustar 00 0000000 0000000 PK aZoa, mimetypeapplication/epub+zipPK aZZF/ META-INF/container.xml] 0DWi7 MAt[nhRѿ7z80oGsrL6Ѥ?-캪L8[hJ"0lGwJvfΣ'q&_5N^pA3wG2^b* WEB~4zPK aZzdw - META-INF/com.apple.ibooks.display-options.xmle @~FJn~Φ)ԿN//q <(bHXn/SvqITXl> 227m|s$3Gmwjs>봍 PK aZdM u EPUB/content.opfr0~ F8qn}$^ uÈE#.3>jkx%̞c92v:B%M6mɒ(z`)Hi$h mFt9hLBimLc#xU=ulȍ MZY}Fw,*٭`[;>fJ'* jLβ8TUA5H%V]dE yLJKɓHز+wU0qümÉ(hCk&iZq]v8h>LU\cuHᠭmZ`+!O]2ΈkUu\9}Z$8zd5"ԝ ddX%ÑJ7 /(ܾ #i*R 7h/-nBauezCl~Jtj2_C7E'top*Xp=y{8ڗ\Uc%b#( nEM*Ԝ/0i{9Yo{xtELd_#HE32D8[6oPK aZ!> EPUB/toc.ncxMo0(6MTI9LiSIօn~mMm~^nmw0H;q{?/ sPqQ'u59@r<C%LjO~$Xj 9V%QI@;ษ\tD8S"Ȧy0n(IJJbDnT+KQl^?$+tuCȸ,S^n t釰e7Q]21"|*WbL2T_,W9r27G}hk肋_PK aZч EPUB/nav.xhtmleRMs WP1vMvvaOj&c$he_MUsnʸ]_7?ch{4d]uqZʇhV8$/5L7LmS%_[_RO_0֎ ZOܒH&.)'G>p6x;I0ѠEV/#OH\5ϱ3