packages feed

pandoc-lua-engine 0.5.0.2 → 0.5.1

raw patch · 21 files changed

+535/−242 lines, 21 filesdep ~citeprocdep ~hsluadep ~hslua-module-path

Dependency ranges changed: citeproc, hslua, hslua-module-path, hslua-module-system, hslua-module-text, hslua-module-version, hslua-module-zip, pandoc

Files

pandoc-lua-engine.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                pandoc-lua-engine-version:             0.5.0.2+version:             0.5.1 build-type:          Simple license:             GPL-2.0-or-later license-file:        COPYING.md@@ -10,12 +10,6 @@ bug-reports:         https://github.com/jgm/pandoc/issues homepage:            https://pandoc.org category:            Text-tested-with:         GHC == 8.6.5-                   , GHC == 8.8.4-                   , GHC == 8.10.7-                   , GHC == 9.0.2-                   , GHC == 9.2.5-                   , GHC == 9.4.4 synopsis:            Lua engine to power custom pandoc conversions description:         This package provides a pandoc scripting engine based on                      Lua.@@ -44,6 +38,10 @@   location:            https://github.com/jgm/pandoc.git   subdir:              pandoc-lua-engine +flag repl+  Description:   Include pandoc Lua repl.+  Default:       True+ common common-options   default-language:    Haskell2010   build-depends:       base              >= 4.12 && < 5@@ -69,6 +67,7 @@   hs-source-dirs:      src   exposed-modules:     Text.Pandoc.Lua   other-modules:       Text.Pandoc.Lua.Custom+                     , Text.Pandoc.Lua.Documentation                      , Text.Pandoc.Lua.Engine                      , Text.Pandoc.Lua.Filter                      , Text.Pandoc.Lua.Global@@ -111,28 +110,30 @@   build-depends:       aeson                      , bytestring            >= 0.9     && < 0.13                      , crypton               >= 0.30    && < 1.1-                     , citeproc              >= 0.8     && < 0.13+                     , citeproc              >= 0.8     && < 0.14                      , containers            >= 0.6.0.1 && < 0.9                      , data-default          >= 0.4     && < 0.9                      , doclayout             >= 0.5     && < 0.6                      , doctemplates          >= 0.11    && < 0.12                      , exceptions            >= 0.8     && < 0.11-                     , hslua                 >= 2.3     && < 2.5+                     , hslua                 >= 2.5     && < 2.6                      , hslua-module-doclayout>= 1.2     && < 1.3-                     , hslua-module-path     >= 1.1     && < 1.2-                     , hslua-module-system   >= 1.2.3   && < 1.3-                     , hslua-module-text     >= 1.1     && < 1.2-                     , hslua-module-version  >= 1.1     && < 1.2-                     , hslua-module-zip      >= 1.1.3   && < 1.2-                     , hslua-repl            >= 0.1.1   && < 0.2+                     , hslua-module-path     >= 1.2     && < 1.3+                     , hslua-module-system   >= 1.3     && < 1.4+                     , hslua-module-text     >= 1.2     && < 1.3+                     , hslua-module-version  >= 1.2     && < 1.3+                     , hslua-module-zip      >= 1.1.5   && < 1.3                      , lpeg                  >= 1.1     && < 1.2                      , mtl                   >= 2.2     && < 2.4-                     , pandoc                >= 3.8     && < 3.9+                     , pandoc                >= 3.9     && < 3.10                      , pandoc-lua-marshal    >= 0.3     && < 0.4                      , pandoc-types          >= 1.22    && < 1.24                      , parsec                >= 3.1     && < 3.2                      , text                  >= 1.1.1   && < 2.2 +  if flag(repl)+     build-depends:    hslua-repl            >= 0.1.1   && < 0.2+     cpp-options:      -DINCLUDE_REPL  test-suite test-pandoc-lua-engine   import:              common-options@@ -145,7 +146,7 @@                      , data-default                      , exceptions            >= 0.8     && < 0.11                      , filepath-                     , hslua                 >= 2.3     && < 2.5+                     , hslua                 >= 2.5     && < 2.6                      , pandoc                      , pandoc-types          >= 1.22    && < 1.24                      , tasty
+ src/Text/Pandoc/Lua/Documentation.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes        #-}+{- |+   Module      : Text.Pandoc.Lua.Documentation+   Copyright   : Copyright © 2026 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>++Render Lua documentation+-}+module Text.Pandoc.Lua.Documentation+  ( renderDocumentation+  ) where++import Data.Default (def)+import Data.List (intersperse)+import Data.Sequence (Seq ((:|>)))+import Data.Version (showVersion)+import HsLua as Lua+import Text.Pandoc.Class (runPure)+import Text.Pandoc.Definition (Pandoc (Pandoc))+import Text.Pandoc.Extensions (extensionsFromList, Extension (..))+import Text.Pandoc.Options (ReaderOptions (readerExtensions))+import Text.Pandoc.Readers (readCommonMark)+import Text.Pandoc.Shared (compactify)+import Text.Pandoc.Walk (walk)++import qualified Data.Text as T+import qualified Text.Pandoc.Builder as B+import qualified Text.Pandoc.UTF8 as UTF8++-- | Render the documentation object as pandoc Blocks+renderDocumentation :: DocumentationObject -> B.Blocks+renderDocumentation = \case+  DocObjectFunction fn -> renderFunctionDoc Nothing fn+  DocObjectModule mdl  -> renderModuleDoc mdl+  DocObjectType tp     -> renderTypeDoc Nothing tp++renderTypeDoc :: Maybe T.Text -> TypeDoc -> B.Blocks+renderTypeDoc mbmodname td = mconcat+  [ B.headerWith (ident, [], []) 1 (B.str $ typeDocName td)+  , parseCommonMark $ typeDocDescription td+  , if null $ typeDocMethods td+    then mempty+    else+      B.header 2 "Methods" <>+      (shiftHeadings 2 . mconcat . map (renderFunctionDoc Nothing) $+       typeDocMethods td)+  ]+ where+  ident = case mbmodname of+    Just modname  -> mconcat [ "type-", modname, ".", typeDocName td ]+    Nothing       -> mconcat [ "type-", typeDocName td ]++-- Shift headings+shiftHeadings :: Int -> B.Blocks -> B.Blocks+shiftHeadings incr blks = flip walk blks $ \case+  B.Header level attr inner -> B.Header (level + incr) attr inner+  x -> x++renderModuleDoc :: ModuleDoc -> B.Blocks+renderModuleDoc moddoc =+  let modname = moduleDocName moddoc+  in mconcat+  [ B.headerWith ("module-" <> modname, [], []) 1+      (B.str $ "Module " <> modname)+  , parseCommonMark (moduleDocDescription moddoc)+  , if null (moduleDocFields moddoc)+    then mempty+    else+      let ident = modname <> "-fields"+      in B.headerWith (ident, [], []) 2 (B.str "Fields") <>+         shiftHeadings 0 (mconcat (map (renderFieldDoc modname)+                                   (moduleDocFields moddoc)))+  , if null (moduleDocFunctions moddoc)+    then mempty+    else+      let ident = modname <> "-functions"+      in B.headerWith (ident, [], []) 2 (B.str "Functions") <>+         (shiftHeadings 2 . mconcat . map (renderFunctionDoc $ Just modname) $+          moduleDocFunctions moddoc)+  , if null (moduleDocTypes moddoc)+    then mempty+    else+      let ident = modname <> "-types"+      in B.headerWith (ident, [], []) 2 (B.str "Types") <>+         (shiftHeadings 2 . mconcat . map (renderTypeDoc $ Just modname) .+          reverse $ moduleDocTypes moddoc)+  ]++parseCommonMark :: T.Text -> B.Blocks+parseCommonMark txt =+  let exts = extensionsFromList+        [ Ext_wikilinks_title_after_pipe+        , Ext_smart+        ]+      result = runPure $ do+        Pandoc _ blks <- readCommonMark (def {readerExtensions = exts}) txt+        return $ B.fromList blks+  in either mempty id result++appendInlines :: B.Blocks -> B.Inlines -> B.Blocks+appendInlines blks inlns = case B.unMany blks of+  front :|> (B.Para xs) -> B.Many front <> B.para (addTo xs)+  front :|> (B.Plain xs) -> B.Many front <> B.plain (addTo xs)+  _ -> blks <> B.para inlns+ where addTo xs = B.fromList xs <> B.space <> inlns++appendType :: B.Blocks -> TypeSpec -> B.Blocks+appendType blks typespec =+  appendInlines blks (B.str "(" <> typeToInlines typespec <> B.str ")")++typeToInlines :: TypeSpec -> B.Inlines+typeToInlines = \case+  bt@BasicType{}   -> builtin $ tystr bt+  NamedType "integer" -> builtin "integer"+  NamedType name   -> B.linkWith ("", ["documented-type"], [])+                        ("#" <> n2t name) mempty $ B.str (n2t name)+  SeqType itemtype -> "{" <> typeToInlines itemtype <> ",...}"+  SumType summands -> mconcat . intersperse (B.str "|") $ map typeToInlines summands+  AnyType          -> "any"+  x                -> tystr x+ where+  tystr = B.str . T.pack . typeSpecToString+  n2t = UTF8.toText . fromName+  builtin = B.spanWith ("", ["builtin-lua-type"], [])++renderFunctionDoc :: Maybe T.Text -> FunctionDoc -> B.Blocks+renderFunctionDoc mbmodule fndoc =+  let name = case mbmodule of+        Just _   ->  T.takeWhileEnd (/= '.') $ funDocName fndoc+        Nothing  -> funDocName fndoc+      ident = funDocName fndoc+      level = 1+      argsString = argslist (funDocParameters fndoc)+      paramToDefItem p = ( B.code $ parameterName p+                         , compactify+                           [ appendType+                               (parseCommonMark $ parameterDescription p)+                               (parameterType p)+                           ]+                         )+      paramlist = B.definitionList . map paramToDefItem $+                  funDocParameters fndoc+  in mconcat+     [ B.headerWith (ident, [], []) level (B.str name)+     , B.plain (B.code $ name <> " (" <> argsString <> ")")+     , parseCommonMark (funDocDescription fndoc)+     , if null (funDocParameters fndoc)+       then mempty+       else B.para "Parameters:" <> paramlist+     , if funDocResults fndoc == ResultsDocList []+       then mempty+       else B.para "Returns:" <> renderResults (funDocResults fndoc)+     , case funDocSince fndoc of+         Nothing -> mempty+         Just version ->+           B.para $ B.emph $ "Since: " <> (B.str . T.pack $ showVersion version)+     ]++renderResults :: ResultsDoc -> B.Blocks+renderResults (ResultsDocMult descr) = parseCommonMark descr+renderResults (ResultsDocList rvd) = B.bulletList $ map renderResultVal rvd+ where+   renderResultVal (ResultValueDoc typespec descr) =+     parseCommonMark descr `appendType` typespec++argslist :: [ParameterDoc] -> T.Text+argslist params =+  -- Expect optional values to come after required values.+  let (required, optional') = break parameterIsOptional params+      reqs = map parameterName required+      opts = map parameterName optional'+  in if null opts+     then T.intercalate ", " reqs+     else T.intercalate ", " reqs <>+          (if null required then "[" else "[, ") <>+          T.intercalate "[, " opts <> T.replicate (length opts) "]"++renderFieldDoc :: T.Text -> FieldDoc -> B.Blocks+renderFieldDoc _modname fd =+  B.headerWith (ident, [], []) 3 (B.str name) <>+  appendType (parseCommonMark $ fieldDocDescription fd) (fieldDocType fd)+ where+  ident = fieldDocName fd+  name = T.takeWhileEnd (/= '.') $ fieldDocName fd
src/Text/Pandoc/Lua/Init.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE RankNTypes        #-} {- |    Module      : Text.Pandoc.Lua.Init-   Copyright   : © 2017-2024 Albert Krewinkel+   Copyright   : © 2017-2026 Albert Krewinkel    License     : GPL-2.0-or-later    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com> 
src/Text/Pandoc/Lua/Marshal/CommonState.hs view
@@ -26,8 +26,10 @@ typeCommonState :: LuaError e => DocumentedType e CommonState typeCommonState = deftype "CommonState" [] [] +-- | Retrieves the common state from Lua peekCommonState :: LuaError e => Peeker e CommonState peekCommonState = peekUD typeCommonState +-- | Pushes the common pandoc state to the Lua stack. pushCommonState :: LuaError e => Pusher e CommonState pushCommonState = pushUD typeCommonState
src/Text/Pandoc/Lua/Marshal/LogMessage.hs view
@@ -13,6 +13,8 @@   , typeLogMessage   ) where +import Control.Applicative (optional)+import Data.Maybe (fromMaybe) import HsLua import Text.Pandoc.Logging (LogMessage, showLogMessage) import qualified Data.Aeson as Aeson@@ -24,6 +26,11 @@       ### liftPure showLogMessage       <#> udparam typeLogMessage "msg" "object"       =#> functionResult pushText "string" "stringified log message"+  , operation Eq $ lambda+      ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+      <#> parameter (optional . peekLogMessage) "a" "LogMessage" ""+      <#> parameter (optional . peekLogMessage) "b" "LogMessage" ""+      =#> functionResult pushBool "boolean" "whether the two are equal"   , operation (CustomOperation "__tojson") $ lambda       ### liftPure Aeson.encode       <#> udparam typeLogMessage "msg" "object"
src/Text/Pandoc/Lua/Module/CLI.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP               #-} {-# LANGUAGE LambdaCase        #-} {-# LANGUAGE OverloadedStrings #-} {- |@@ -15,28 +16,27 @@ import Control.Applicative ((<|>)) import Data.Version (makeVersion) import HsLua-import HsLua.REPL (defaultConfig, replWithEnv, setup) 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 = Module-  { moduleName = "pandoc.cli"-  , moduleDescription =+documentedModule = defmodule "pandoc.cli"+  `withDescription`       "Command line options and argument parsing."-  , moduleFields =-      [ Field-        { fieldName = "default_options"-        , fieldType = "table"-        , fieldDescription = "Default CLI options, using a JSON-like " <>-          "representation."-        , fieldPushValue = pushViaJSON defaultOpts-        }+  `withFields`+      [ deffield "default_options"+        `withType` "table"+        `withDescription`+          "Default CLI options, using a JSON-like representation."+        `withValue` pushViaJSON defaultOpts       ]-  , moduleFunctions =+  `withFunctions`       [ defun "parse_options"         ### parseOptions         <#> parameter peekArgs "{string,...}" "args"@@ -49,12 +49,10 @@            , "scripts, taking the list of arguments from the global `arg`."            ]         `since` makeVersion [3, 0]-+#ifdef INCLUDE_REPL       , repl `since` makeVersion [3, 1, 2]+#endif       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  where   peekArgs idx =     (,)@@ -67,6 +65,7 @@       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"@@ -123,3 +122,4 @@             copyval     copyval     pop 1  -- global table+#endif
src/Text/Pandoc/Lua/Module/Format.hs view
@@ -23,16 +23,11 @@  -- | The "pandoc.format" module. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.format"-  , moduleDescription = T.unlines+documentedModule = defmodule "pandoc.format"+  `withDescription` T.unlines     [ "Information about the formats supported by pandoc."     ]-  , moduleFields = []-  , moduleOperations = []-  , moduleFunctions = functions-  , moduleTypeInitializers = []-  }+  `withFunctions` functions  -- | Extension module functions. functions :: [DocumentedFunction PandocError]
src/Text/Pandoc/Lua/Module/Image.hs view
@@ -34,22 +34,9 @@  -- | The @pandoc.image@ module specification. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.image"-  , moduleDescription = "Basic image querying functions."-  , moduleFields = fields-  , moduleFunctions = functions-  , moduleOperations = []-  , moduleTypeInitializers = []-  }------- Fields------- | Exported fields.-fields :: LuaError e => [Field e]-fields = []+documentedModule = defmodule "pandoc.image"+  `withDescription` "Basic image querying functions."+  `withFunctions` functions  -- -- Functions
src/Text/Pandoc/Lua/Module/JSON.hs view
@@ -35,15 +35,11 @@  -- | The @aeson@ module specification. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.json"-  , moduleDescription = "JSON module to work with JSON; " <>-                        "based on the Aeson Haskell package."-  , moduleFields = fields-  , moduleFunctions = functions-  , moduleOperations = []-  , moduleTypeInitializers = []-  }+documentedModule = defmodule "pandoc.json"+  `withDescription`+    "JSON module to work with JSON; based on the Aeson Haskell package."+  `withFields` fields+  `withFunctions` functions  -- -- Fields@@ -57,12 +53,10 @@  -- | The value used to represent the JSON @null@. null :: LuaError e => Field e-null = Field-  { fieldName = "null"-  , fieldType = "light userdata"-  , fieldDescription = "Value used to represent the `null` JSON value."-  , fieldPushValue = pushValue Aeson.Null-  }+null = deffield "null"+  `withType` "light userdata"+  `withDescription` "Value used to represent the `null` JSON value."+  `withValue` pushValue Aeson.Null  -- -- Functions
src/Text/Pandoc/Lua/Module/Log.hs view
@@ -26,12 +26,11 @@  -- | Push the pandoc.log module on the Lua stack. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.log"-  , moduleDescription =+documentedModule = defmodule "pandoc.log"+  `withDescription`       "Access to pandoc's logging system."-  , moduleFields = []-  , moduleFunctions =+  `withFields` []+  `withFunctions`       [ defun "info"         ### (\msg -> do                 -- reporting levels:@@ -78,9 +77,6 @@            ]         `since` makeVersion [3, 2]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  -- | 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
src/Text/Pandoc/Lua/Module/MediaBag.hs view
@@ -37,9 +37,8 @@ -- MediaBag submodule -- documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.mediabag"-  , moduleDescription = T.unlines+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."@@ -50,8 +49,7 @@     , ""     , "    local mb = require 'pandoc.mediabag'"     ]-  , moduleFields = []-  , moduleFunctions =+  `Lua.withFunctions`       [ delete  `since` makeVersion [2,7,3]       , empty   `since` makeVersion [2,7,3]       , fetch   `since` makeVersion [2,0]@@ -63,9 +61,6 @@       , make_data_uri `since` makeVersion [3,7,1]       , write   `since` makeVersion [3,0]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  -- | Delete a single item from the media bag. delete :: DocumentedFunction PandocError@@ -109,8 +104,9 @@           setMediaBag $ MB.insertMedia fp mmime contents mb           return (Lua.NumResults 0))   <#> stringParam "filepath" "filename and path relative to the output folder."-  <#> opt (textParam "mimetype"-           "the item's MIME type; omit if unknown or unavailable.")+  <#> 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."   =#> []
src/Text/Pandoc/Lua/Module/Pandoc.hs view
@@ -30,6 +30,8 @@ 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@@ -57,22 +59,19 @@ import qualified HsLua as Lua import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BSL-import qualified Data.Set as Set import qualified Data.Text as T import qualified Text.Pandoc.UTF8 as UTF8  documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc"-  , moduleDescription = T.unlines+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."     ]-  , moduleFields = readersField : writersField :+  `withFields` readersField : writersField :                    stringConstants ++ [inlineField, blockField]-  , moduleOperations = []-  , moduleFunctions = mconcat+  `withFunctions` mconcat       [ [mkPandoc, mkMeta]       , metaValueConstructors       , blockConstructors@@ -82,60 +81,57 @@       , otherConstructors       , functions       ]-  , moduleTypeInitializers =-    [ initType typePandoc-    , initType typeBlock-    , initType typeInline-    ]-  }+  `associateType` typePandoc+  `associateType` typeBlock+  `associateType` typeInline  -- | Set of input formats accepted by @read@. readersField :: Field PandocError-readersField = Field-  { fieldName = "readers"-  , fieldType = "table"-  , fieldDescription = T.unlines+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`."     ]-  , fieldPushValue = pushSet pushText $-                     Set.fromList (map fst (readers @PandocLua))-  }+  `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 = Field-  { fieldName = "writers"-  , fieldType = "table"-  , fieldDescription = T.unlines+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`."     ]-  , fieldPushValue = pushSet pushText $-                     Set.fromList (map fst (writers @PandocLua))-  }+  `withValue` pushKeyValuePairs pushText (pushText . writerType)+                     (writers @PandocLua)+ where+  writerType = \case+    TextWriter {} -> "text"+    ByteStringWriter {} -> "bytestring"  -- | Inline table field inlineField :: Field PandocError-inlineField = Field-  { fieldName = "Inline"-  , fieldType = "table"-  , fieldDescription = "Inline constructors, nested under 'constructors'."+inlineField = deffield "Inline"+  `withType` "table"+  `withDescription` "Inline constructors, nested under 'constructors'."   -- the nesting happens for historical reasons and should probably be   -- changed.-  , fieldPushValue = pushWithConstructorsSubtable inlineConstructors-  }+  `withValue` pushWithConstructorsSubtable inlineConstructors  -- | @Block@ module field blockField :: Field PandocError-blockField = Field-  { fieldName = "Block"-  , fieldType = "table"-  , fieldDescription = "Inline constructors, nested under 'constructors'."+blockField = deffield "Block"+  `withType` "table"+  `withDescription` "Inline constructors, nested under 'constructors'."   -- the nesting happens for historical reasons and should probably be   -- changed.-  , fieldPushValue = pushWithConstructorsSubtable blockConstructors-  }+  `withValue` pushWithConstructorsSubtable blockConstructors  pushWithConstructorsSubtable :: [DocumentedFunction PandocError]                              -> LuaE PandocError ()@@ -213,12 +209,10 @@         , constrs (Proxy @Alignment)         , constrs (Proxy @CitationMode)         ]-      toField s = Field-        { fieldName = T.pack s-        , fieldType = "string"-        , fieldDescription = T.pack s-        , fieldPushValue = pushString s-        }+      toField s = deffield (Name $ UTF8.fromString s)+        `withType` "string"+        `withDescription` T.pack s+        `withValue` pushString s   in map toField nullaryConstructors  functions :: [DocumentedFunction PandocError]@@ -294,6 +288,37 @@     <#> 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@@ -429,3 +454,69 @@    -- 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
src/Text/Pandoc/Lua/Module/Path.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE TypeApplications    #-} {- |    Module      : Text.Pandoc.Lua.Module.Path-   Copyright   : © 2019-2024 Albert Krewinkel+   Copyright   : © 2019-2026 Albert Krewinkel    License     : GNU GPL, version 2 or above     Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>@@ -22,14 +22,13 @@  -- | Push the pandoc.system module on the Lua stack. documentedModule :: forall e. LuaError e => Module e-documentedModule = Module-  { moduleName = "pandoc.path"-  , moduleDescription = moduleDescription @e MPath.documentedModule-  , moduleFields =+documentedModule = defmodule "pandoc.path"+  `withDescription` moduleDescription @e MPath.documentedModule+  `withFields`       [ MPath.separator       , MPath.search_path_separator       ]-  , moduleFunctions =+  `withFunctions`       [ MPath.directory              `since` v[2,12]       , MSystem.exists               `since` v[3,7,1]       , MPath.filename               `since` v[2,12]@@ -43,8 +42,5 @@       , MPath.split_search_path      `since` v[2,12]       , MPath.treat_strings_as_paths `since` v[2,12]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  where   v = makeVersion
src/Text/Pandoc/Lua/Module/Scaffolding.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} {- |    Module      : Text.Pandoc.Lua.Module.Scaffolding-   Copyright   : Copyright © 2022-2024 Albert Krewinkel, John MacFarlane+   Copyright   : Copyright © 2022-2026 Albert Krewinkel, John MacFarlane    License     : GNU GPL, version 2 or above    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com> @@ -18,28 +18,20 @@  -- | The "pandoc.template" module. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.scaffolding"-  , moduleDescription = T.unlines-    [ "Scaffolding for custom writers."-    ]-  , moduleFields = [writerScaffolding]-  , moduleOperations = []-  , moduleFunctions = []-  , moduleTypeInitializers = []-  }+documentedModule = defmodule "pandoc.scaffolding"+  `withDescription` "Scaffolding for custom writers."+  `withFields` [writerScaffolding]  -- | Template module functions. writerScaffolding :: Field PandocError-writerScaffolding = Field-  { fieldName = "Writer"-  , fieldType = "table"-  , fieldDescription = T.unlines+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"     ]-  , fieldPushValue = do+  `withValue` do       pushWriterScaffolding       -- pretend that it's a submodule so we get better error messages       getfield registryindex loaded@@ -50,5 +42,4 @@       getfield (nth 2) "Block" *> setfield (nth 2) (submod "Writer.Block")        pop 1 -- remove "LOADED_TABLE"-  }  where submod x = moduleName documentedModule <> "." <> x
src/Text/Pandoc/Lua/Module/Structure.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {- |    Module      : Text.Pandoc.Lua.Module.Structure-   Copyright   : © 2023-2025 Albert Krewinkel <albert+pandoc@tarleb.com>+   Copyright   : © 2023-2026 Albert Krewinkel <albert+pandoc@tarleb.com>    License     : GPL-2.0-or-later    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com> @@ -17,11 +17,11 @@ 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 )+             , pushText, since, top, withDescription, withFunctions ) import Text.Pandoc.Chunks ( ChunkedDoc (..), PathTemplate (..)                           , tocToList, splitIntoChunks ) import Text.Pandoc.Definition (Pandoc (..), Block)@@ -41,22 +41,17 @@  -- | Push the pandoc.structure module on the Lua stack. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.structure"-  , moduleDescription =+documentedModule = defmodule "pandoc.structure"+  `withDescription`     "Access to the higher-level document structure, including " <>     "hierarchical sections and the table of contents."-  , moduleFields = []-  , moduleFunctions =+  `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]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  make_sections :: LuaError e => DocumentedFunction e make_sections = defun "make_sections"
src/Text/Pandoc/Lua/Module/System.hs view
@@ -26,34 +26,27 @@  -- | Push the pandoc.system module on the Lua stack. documentedModule :: forall e. LuaError e => Module e-documentedModule = Module-  { moduleName = "pandoc.system"-  , moduleDescription = moduleDescription @e MSys.documentedModule-  , moduleFields =-      [ arch-      , os-      ]-  , moduleFunctions =-      [ cputime                                        `since` v[3,1,1]-      , setName "command" cmd                          `since` v[3,7,1]-      , setName "copy" cp                              `since` v[3,7,1]-      , setName "environment" env                      `since` v[2,7,3]-      , setName "get_working_directory" getwd          `since` v[2,8]-      , setName "list_directory" ls                    `since` v[2,19]-      , setName "make_directory" mkdir                 `since` v[2,19]-      , read_file                                      `since` v[3,7,1]-      , rename                                         `since` v[3,7,1]-      , setName "remove" rm                            `since` v[3,7,1]-      , setName "remove_directory" rmdir               `since` v[2,19]-      , times                                          `since` v[3,7,1]-      , setName "with_environment" with_env            `since` v[2,7,3]-      , setName "with_temporary_directory" with_tmpdir `since` v[2,8]-      , setName "with_working_directory" with_wd       `since` v[2,7,3]-      , write_file                                     `since` v[3,7,1]-      , xdg                                            `since` v[3,7,1]+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]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }  where   v = makeVersion
src/Text/Pandoc/Lua/Module/Template.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE OverloadedStrings #-} {- |    Module      : Text.Pandoc.Lua.Module.Template-   Copyright   : Copyright © 2022-2024 Albert Krewinkel, John MacFarlane-   License     : GNU GPL, version 2 or above+   Copyright   : Copyright © 2022-2026 Albert Krewinkel, John MacFarlane+   License     : GPL-2.0-or-later    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>  Lua module to handle pandoc templates.@@ -28,16 +28,10 @@  -- | The "pandoc.template" module. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.template"-  , moduleDescription = T.unlines-    [ "Handle pandoc templates."-    ]-  , moduleFields = []-  , moduleOperations = []-  , moduleFunctions = functions-  , moduleTypeInitializers = [initType typeTemplate]-  }+documentedModule = defmodule "pandoc.template"+  `withDescription` "Handle pandoc templates."+  `withFunctions` functions+  `associateType` typeTemplate  -- | Template module functions. functions :: [DocumentedFunction PandocError]
src/Text/Pandoc/Lua/Module/Text.hs view
@@ -22,9 +22,8 @@  -- | The @aeson@ module specification. documentedModule :: Module PandocError-documentedModule = TM.documentedModule-  { moduleName = "pandoc.text"-  , moduleFunctions =+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]@@ -35,7 +34,7 @@     , TM.toencoding   `since` v[3,0]     , TM.upper        `since` v[2,0,3]     ]-  , moduleDescription = T.unlines+  `withDescription` T.unlines     [ "UTF-8 aware text manipulation functions, implemented in Haskell."     , ""     , "The text module can also be loaded under the name `text`, although"@@ -49,7 +48,6 @@     , "end"     , "```"     ]-  }  where   v = makeVersion 
src/Text/Pandoc/Lua/Module/Types.hs view
@@ -15,19 +15,19 @@  import Data.Version (makeVersion) import HsLua ( Module (..), (###), (<#>), (=#>)-             , defun, functionResult, parameter, since)+             , 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 = Module-  { moduleName = "pandoc.types"-  , moduleDescription =+documentedModule = defmodule "pandoc.types"+  `withDescription`       "Constructors for types that are not part of the pandoc AST."-  , moduleFields = []-  , moduleFunctions =+  `withFunctions`       [ defun "Version"         ### return         <#> parameter peekVersionFuzzy "string|number|{integer,...}|Version"@@ -40,6 +40,3 @@         =#> functionResult pushVersion "Version" "New Version object."         `since` makeVersion [2,7,3]       ]-  , moduleOperations = []-  , moduleTypeInitializers = []-  }
src/Text/Pandoc/Lua/Module/Utils.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE TypeApplications    #-} {- |    Module      : Text.Pandoc.Lua.Module.Utils-   Copyright   : Copyright © 2017-2024 Albert Krewinkel+   Copyright   : © 2017-2026 Albert Krewinkel    License     : GNU GPL, version 2 or above     Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>@@ -19,6 +19,7 @@  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)@@ -28,12 +29,17 @@ import HsLua.Module.Version (peekVersionFuzzy, pushVersion) import Text.Pandoc.Citeproc (getReferences, processCitations) import Text.Pandoc.Definition-import Text.Pandoc.Error (PandocError)+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@@ -44,18 +50,15 @@  -- | Push the "pandoc.utils" module to the Lua stack. documentedModule :: Module PandocError-documentedModule = Module-  { moduleName = "pandoc.utils"-  , moduleDescription = T.unlines+documentedModule = defmodule "pandoc.utils"+  `withDescription` T.unlines     [ "This module exposes internal pandoc functions and utility"     , "functions."     ]-  , moduleFields = []-  , moduleOperations = []-  , moduleTypeInitializers = []-  , moduleFunctions =+  `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]@@ -71,13 +74,11 @@      , defun "Version"       ### liftPure (id @Version)-      <#> parameter peekVersionFuzzy-            "version string, list of integers, or integer"+      <#> parameter peekVersionFuzzy "Version|string|{integer,...}|number"             "v" "version description"       =#> functionResult pushVersion "Version" "new Version object"       #? "Creates a Version object."     ]-  }  where   v = makeVersion @@ -130,6 +131,35 @@       , "    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@@ -210,7 +240,7 @@ normalize_date = defun "normalize_date"   ### liftPure Shared.normalizeDate   <#> parameter peekText "string" "date" "the date string"-  =#> functionResult (maybe pushnil pushText) "string or nil"+  =#> 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"@@ -325,7 +355,8 @@          , (fmap (const "") . peekAttr)          , (fmap (const "") . peekListAttributes)          ] idx)-  <#> parameter pure "AST element" "element" "some pandoc AST element"+  <#> 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
test/lua/module/pandoc.lua view
@@ -463,6 +463,48 @@     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 ()