Lua: use hslua module abstraction where possible

This will make it easier to generate module documentation in the future.
This commit is contained in:
Albert Krewinkel 2021-10-29 17:08:03 +02:00
parent e1cf0ad1be
commit f4d9b443d8
No known key found for this signature in database
GPG key ID: 388DC0B21F631124
13 changed files with 382 additions and 409 deletions

View file

@ -45,125 +45,19 @@ local utils = M.utils
-- @section -- @section
-- @local -- @local
--- Create a new indexing function.
-- @param template function template
-- @param indices list of indices, starting with the most deeply nested
-- @return newly created function
-- @local
function make_indexing_function(template, ...)
local indices = {...}
local loadstring = loadstring or load
local bracketed = {}
for i = 1, #indices do
local idx = indices[#indices - i + 1]
bracketed[i] = type(idx) == 'number'
and string.format('[%d]', idx)
or string.format('.%s', idx)
end
local fnstr = string.format('return ' .. template, table.concat(bracketed))
return assert(loadstring(fnstr))()
end
--- Create accessor functions using a function template.
-- @param fn_template function template in which '%s' is replacd with indices
-- @param accessors list of accessors
-- @return mapping from accessor names to accessor functions
-- @local
local function create_accessor_functions (fn_template, accessors)
local res = {}
function add_accessors(acc, ...)
if type(acc) == 'string' then
res[acc] = make_indexing_function(fn_template, ...)
elseif type(acc) == 'table' and #acc == 0 and next(acc) then
-- Named substructure: the given names are accessed via the substructure,
-- but the accessors are also added to the result table, enabling direct
-- access from the parent element. Mainly used for `attr`.
local name, substructure = next(acc)
res[name] = make_indexing_function(fn_template, ...)
for _, subname in ipairs(substructure) do
res[subname] = make_indexing_function(fn_template, subname, ...)
end
else
for i = 1, #(acc or {}) do
add_accessors(acc[i], i, ...)
end
end
end
add_accessors(accessors)
return res
end
--- Get list of top-level fields from field descriptor table.
-- E.g.: `top_level_fields{'foo', {bar='baz'}, {'qux', 'quux'}}`
-- gives {'foo, 'bar', 'qux', 'quux'}
-- @local
local function top_level_fields (fields)
local result = List:new{}
for _, v in ipairs(fields) do
if type(v) == 'string' then
table.insert(result, v)
elseif type(v) == 'table' and #v == 0 and next(v) then
table.insert(result, (next(v)))
else
result:extend(top_level_fields(v))
end
end
return result
end
--- Creates a function which behaves like next, but respects field names.
-- @local
local function make_next_function (fields)
local field_indices = {}
for i, f in ipairs(fields) do
field_indices[f] = i
end
return function (t, field)
local raw_idx = field == nil and 0 or field_indices[field]
local next_field = fields[raw_idx + 1]
return next_field, t[next_field]
end
end
--- Create a new table which allows to access numerical indices via accessor --- Create a new table which allows to access numerical indices via accessor
-- functions. -- functions.
-- @local -- @local
local function create_accessor_behavior (tag, accessors) local function create_accessor_behavior (tag)
local behavior = {tag = tag} local behavior = {tag = tag}
behavior.getters = create_accessor_functions(
'function (x) return x.c%s end',
accessors
)
behavior.setters = create_accessor_functions(
'function (x, v) x.c%s = v end',
accessors
)
behavior.__eq = utils.equals behavior.__eq = utils.equals
behavior.__index = function(t, k) behavior.__index = function(t, k)
if getmetatable(t).getters[k] then if k == "t" then
return getmetatable(t).getters[k](t)
elseif k == "t" then
return getmetatable(t)["tag"] return getmetatable(t)["tag"]
else
return getmetatable(t)[k]
end
end
behavior.__newindex = function(t, k, v)
if getmetatable(t).setters[k] then
getmetatable(t).setters[k](t, v)
else
rawset(t, k, v)
end end
end end
behavior.__pairs = function (t) behavior.__pairs = function (t)
if accessors == nil then return next, t
return next, t
end
local iterable_fields = type(accessors) == 'string'
and {accessors}
or top_level_fields(accessors)
return make_next_function(iterable_fields), t
end end
return behavior return behavior
end end
@ -242,8 +136,8 @@ end
-- @param fn Function to be called when constructing a new element -- @param fn Function to be called when constructing a new element
-- @param accessors names to use as accessors for numerical fields -- @param accessors names to use as accessors for numerical fields
-- @return function that constructs a new element -- @return function that constructs a new element
function AstElement:create_constructor(tag, fn, accessors) function AstElement:create_constructor(tag, fn)
local constr = self:make_subtype(tag, create_accessor_behavior(tag, accessors)) local constr = self:make_subtype(tag, create_accessor_behavior(tag))
function constr:new(...) function constr:new(...)
return setmetatable(fn(...), self.behavior) return setmetatable(fn(...), self.behavior)
end end
@ -348,8 +242,4 @@ function M.MetaBool(bool)
return bool return bool
end end
------------------------------------------------------------------------
-- Functions which have moved to different modules
M.sha1 = utils.sha1
return M return M

View file

@ -14,6 +14,7 @@ Types and functions for running Lua filters.
-} -}
module Text.Pandoc.Lua.Filter ( LuaFilterFunction module Text.Pandoc.Lua.Filter ( LuaFilterFunction
, LuaFilter , LuaFilter
, peekLuaFilter
, runFilterFile , runFilterFile
, walkInlines , walkInlines
, walkInlineLists , walkInlineLists
@ -68,20 +69,24 @@ newtype LuaFilterFunction = LuaFilterFunction Lua.Reference
newtype LuaFilter = LuaFilter (Map Name LuaFilterFunction) newtype LuaFilter = LuaFilter (Map Name LuaFilterFunction)
instance Peekable LuaFilter where instance Peekable LuaFilter where
peek idx = do peek = Lua.forcePeek . peekLuaFilter
let constrs = listOfInlinesFilterName
: listOfBlocksFilterName -- | Retrieves a LuaFilter object from the stack.
: metaFilterName peekLuaFilter :: LuaError e => Peeker e LuaFilter
: pandocFilterNames peekLuaFilter idx = do
++ blockElementNames let constrs = listOfInlinesFilterName
++ inlineElementNames : listOfBlocksFilterName
let go constr acc = do : metaFilterName
Lua.getfield idx constr : pandocFilterNames
filterFn <- registerFilterFunction ++ blockElementNames
return $ case filterFn of ++ inlineElementNames
Nothing -> acc let go constr acc = Lua.liftLua $ do
Just fn -> Map.insert constr fn acc Lua.getfield idx constr
LuaFilter <$!> foldrM go Map.empty constrs filterFn <- registerFilterFunction
return $ case filterFn of
Nothing -> acc
Just fn -> Map.insert constr fn acc
LuaFilter <$!> foldrM go Map.empty constrs
-- | Register the function at the top of the stack as a filter function in the -- | Register the function at the top of the stack as a filter function in the
-- registry. -- registry.

View file

@ -45,6 +45,7 @@ module Text.Pandoc.Lua.Marshaling.AST
, pushCitation , pushCitation
, pushInline , pushInline
, pushListAttributes , pushListAttributes
, pushMeta
, pushMetaValue , pushMetaValue
, pushPandoc , pushPandoc
) where ) where

View file

@ -204,26 +204,30 @@ peekAttrTable idx = do
return $ ident `seq` classes `seq` attribs `seq` return $ ident `seq` classes `seq` attribs `seq`
(ident, classes, attribs) (ident, classes, attribs)
mkAttr :: LuaError e => LuaE e NumResults -- | Constructor for 'Attr'.
mkAttr = do mkAttr :: LuaError e => DocumentedFunction e
attr <- ltype (nthBottom 1) >>= \case mkAttr = defun "Attr"
TypeString -> forcePeek $ do ### (ltype (nthBottom 1) >>= \case
mident <- optional (peekText (nthBottom 1)) TypeString -> forcePeek $ do
mclass <- optional (peekList peekText (nthBottom 2)) mident <- optional (peekText (nthBottom 1))
mattribs <- optional (peekAttribs (nthBottom 3)) mclass <- optional (peekList peekText (nthBottom 2))
return (fromMaybe "" mident, fromMaybe [] mclass, fromMaybe [] mattribs) mattribs <- optional (peekAttribs (nthBottom 3))
TypeTable -> forcePeek $ peekAttrTable (nthBottom 1) return ( fromMaybe "" mident
TypeUserdata -> forcePeek $ peekUD typeAttr (nthBottom 1) <|> do , fromMaybe [] mclass
attrList <- peekUD typeAttributeList (nthBottom 1) , fromMaybe [] mattribs)
return ("", [], attrList) TypeTable -> forcePeek $ peekAttrTable (nthBottom 1)
TypeNil -> pure nullAttr TypeUserdata -> forcePeek $ peekUD typeAttr (nthBottom 1) <|> do
TypeNone -> pure nullAttr attrList <- peekUD typeAttributeList (nthBottom 1)
x -> failLua $ "Cannot create Attr from " ++ show x return ("", [], attrList)
pushAttr attr TypeNil -> pure nullAttr
return 1 TypeNone -> pure nullAttr
x -> failLua $ "Cannot create Attr from " ++ show x)
=#> functionResult pushAttr "Attr" "new Attr object"
mkAttributeList :: LuaError e => LuaE e NumResults -- | Constructor for 'AttributeList'.
mkAttributeList = do mkAttributeList :: LuaError e => DocumentedFunction e
attribs <- forcePeek $ peekAttribs (nthBottom 1) mkAttributeList = defun "AttributeList"
pushUD typeAttributeList attribs ### return
return 1 <#> parameter peekAttribs "table|AttributeList" "attribs" "an attribute list"
=#> functionResult (pushUD typeAttributeList) "AttributeList"
"new AttributeList object"

View file

@ -1,112 +1,126 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{- | {- |
Module : Text.Pandoc.Lua.Module.MediaBag Module : Text.Pandoc.Lua.Module.MediaBag
Copyright : Copyright © 2017-2021 Albert Krewinkel Copyright : Copyright © 2017-2021 Albert Krewinkel
License : GNU GPL, version 2 or above License : GNU GPL, version 2 or above
Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de> Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
Stability : alpha
The lua module @pandoc.mediabag@. The Lua module @pandoc.mediabag@.
-} -}
module Text.Pandoc.Lua.Module.MediaBag module Text.Pandoc.Lua.Module.MediaBag
( pushModule ( documentedModule
) where ) where
import Prelude hiding (lookup) import Prelude hiding (lookup)
import Control.Monad (zipWithM_) import Data.Maybe (fromMaybe)
import HsLua (LuaE, NumResults, Optional) import HsLua ( LuaE, DocumentedFunction, Module (..)
import HsLua.Marshalling (pushIterator) , (<#>), (###), (=#>), (=?>), defun, functionResult
, optionalParameter , parameter)
import Text.Pandoc.Class.CommonState (CommonState (..)) import Text.Pandoc.Class.CommonState (CommonState (..))
import Text.Pandoc.Class.PandocMonad (fetchItem, getMediaBag, modifyCommonState, import Text.Pandoc.Class.PandocMonad (fetchItem, getMediaBag, modifyCommonState,
setMediaBag) setMediaBag)
import Text.Pandoc.Error (PandocError) import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.Marshaling () import Text.Pandoc.Lua.Marshaling ()
import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua, addFunction) import Text.Pandoc.Lua.Marshaling.List (pushPandocList)
import Text.Pandoc.Lua.PandocLua (unPandocLua)
import Text.Pandoc.MIME (MimeType) import Text.Pandoc.MIME (MimeType)
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import qualified HsLua as Lua import qualified HsLua as Lua
import qualified Text.Pandoc.MediaBag as MB import qualified Text.Pandoc.MediaBag as MB
-- --
-- MediaBag submodule -- MediaBag submodule
-- --
pushModule :: PandocLua NumResults documentedModule :: Module PandocError
pushModule = do documentedModule = Module
liftPandocLua Lua.newtable { moduleName = "pandoc.mediabag"
addFunction "delete" delete , moduleDescription = "mediabag access"
addFunction "empty" empty , moduleFields = []
addFunction "insert" insert , moduleFunctions =
addFunction "items" items [ delete
addFunction "lookup" lookup , empty
addFunction "list" list , fetch
addFunction "fetch" fetch , insert
return 1 , items
, list
, lookup
]
, moduleOperations = []
}
-- | Delete a single item from the media bag. -- | Delete a single item from the media bag.
delete :: FilePath -> PandocLua NumResults delete :: DocumentedFunction PandocError
delete fp = 0 <$ modifyCommonState delete = defun "delete"
(\st -> st { stMediaBag = MB.deleteMedia fp (stMediaBag st) }) ### (\fp -> unPandocLua $ modifyCommonState
(\st -> st { stMediaBag = MB.deleteMedia fp (stMediaBag st) }))
<#> parameter Lua.peekString "string" "filepath" "filename of item to delete"
=#> []
-- | Delete all items from the media bag. -- | Delete all items from the media bag.
empty :: PandocLua NumResults empty :: DocumentedFunction PandocError
empty = 0 <$ modifyCommonState (\st -> st { stMediaBag = mempty }) empty = defun "empty"
### unPandocLua (modifyCommonState (\st -> st { stMediaBag = mempty }))
=#> []
-- | Insert a new item into the media bag. -- | Insert a new item into the media bag.
insert :: FilePath insert :: DocumentedFunction PandocError
-> Optional MimeType insert = defun "insert"
-> BL.ByteString ### (\fp mmime contents -> unPandocLua $ do
-> PandocLua NumResults mb <- getMediaBag
insert fp optionalMime contents = do setMediaBag $ MB.insertMedia fp mmime contents mb
mb <- getMediaBag return (Lua.NumResults 0))
setMediaBag $ MB.insertMedia fp (Lua.fromOptional optionalMime) contents mb <#> parameter Lua.peekString "string" "filepath" "item file path"
return (Lua.NumResults 0) <#> optionalParameter Lua.peekText "string" "mimetype" "the item's MIME type"
<#> parameter Lua.peekLazyByteString "string" "contents" "binary contents"
=?> "Nothing"
-- | Returns iterator values to be used with a Lua @for@ loop. -- | Returns iterator values to be used with a Lua @for@ loop.
items :: PandocLua NumResults items :: DocumentedFunction PandocError
items = do items = defun "items"
mb <- getMediaBag ### (do
liftPandocLua $ do mb <-unPandocLua getMediaBag
let pushItem (fp, mimetype, contents) = do let pushItem (fp, mimetype, contents) = do
Lua.pushString fp Lua.pushString fp
Lua.pushText mimetype Lua.pushText mimetype
Lua.pushByteString $ BL.toStrict contents Lua.pushByteString $ BL.toStrict contents
return (Lua.NumResults 3) return (Lua.NumResults 3)
pushIterator pushItem (MB.mediaItems mb) Lua.pushIterator pushItem (MB.mediaItems mb))
=?> "Iterator triple"
lookup :: FilePath -- | Function to lookup a value in the mediabag.
-> PandocLua NumResults lookup :: DocumentedFunction PandocError
lookup fp = do lookup = defun "lookup"
res <- MB.lookupMedia fp <$> getMediaBag ### (\fp -> unPandocLua (MB.lookupMedia fp <$> getMediaBag) >>= \case
liftPandocLua $ case res of Nothing -> 1 <$ Lua.pushnil
Nothing -> 1 <$ Lua.pushnil Just item -> 2 <$ do
Just item -> do Lua.pushText $ MB.mediaMimeType item
Lua.push $ MB.mediaMimeType item Lua.pushLazyByteString $ MB.mediaContents item)
Lua.push $ MB.mediaContents item <#> parameter Lua.peekString "string" "filepath" "path of item to lookup"
return 2 =?> "MIME type and contents"
list :: PandocLua NumResults -- | Function listing all mediabag items.
list = do list :: DocumentedFunction PandocError
dirContents <- MB.mediaDirectory <$> getMediaBag list = defun "list"
liftPandocLua $ do ### (unPandocLua (MB.mediaDirectory <$> getMediaBag))
Lua.newtable =#> functionResult (pushPandocList pushEntry) "table" "list of entry triples"
zipWithM_ addEntry [1..] dirContents
return 1
where where
addEntry :: Lua.Integer -> (FilePath, MimeType, Int) -> LuaE PandocError () pushEntry :: (FilePath, MimeType, Int) -> LuaE PandocError ()
addEntry idx (fp, mimeType, contentLength) = do pushEntry (fp, mimeType, contentLength) = do
Lua.newtable Lua.newtable
Lua.push ("path" :: T.Text) *> Lua.push fp *> Lua.rawset (-3) Lua.pushName "path" *> Lua.pushString fp *> Lua.rawset (-3)
Lua.push ("type" :: T.Text) *> Lua.push mimeType *> Lua.rawset (-3) Lua.pushName "type" *> Lua.pushText mimeType *> Lua.rawset (-3)
Lua.push ("length" :: T.Text) *> Lua.push contentLength *> Lua.rawset (-3) Lua.pushName "length" *> Lua.pushIntegral contentLength *> Lua.rawset (-3)
Lua.rawseti (-2) idx
fetch :: T.Text -- | Lua function to retrieve a new item.
-> PandocLua NumResults fetch :: DocumentedFunction PandocError
fetch src = do fetch = defun "fetch"
(bs, mimeType) <- fetchItem src ### (\src -> do
liftPandocLua . Lua.push $ maybe "" T.unpack mimeType (bs, mimeType) <- unPandocLua $ fetchItem src
liftPandocLua $ Lua.push bs Lua.pushText $ fromMaybe "" mimeType
return 2 -- returns 2 values: contents, mimetype Lua.pushByteString bs
return 2)
<#> parameter Lua.peekText "string" "src" "URI to fetch"
=?> "Returns two string values: the fetched contents and the mimetype."

View file

@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
@ -14,11 +15,12 @@ Pandoc module for lua.
-} -}
module Text.Pandoc.Lua.Module.Pandoc module Text.Pandoc.Lua.Module.Pandoc
( pushModule ( pushModule
, documentedModule
) where ) where
import Prelude hiding (read) import Prelude hiding (read)
import Control.Applicative ((<|>), optional) import Control.Applicative ((<|>))
import Control.Monad ((>=>), (<$!>), forM_, when) import Control.Monad ((<$!>), forM_, when)
import Control.Monad.Catch (catch, throwM) import Control.Monad.Catch (catch, throwM)
import Control.Monad.Except (throwError) import Control.Monad.Except (throwError)
import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr) import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr)
@ -26,13 +28,14 @@ import Data.Default (Default (..))
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Proxy (Proxy (Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.Text (Text) import Data.Text (Text)
import HsLua as Lua hiding (Div, pushModule) import HsLua hiding (Div, pushModule)
import HsLua.Class.Peekable (PeekError) import HsLua.Class.Peekable (PeekError)
import System.Exit (ExitCode (..)) import System.Exit (ExitCode (..))
import Text.Pandoc.Class.PandocIO (runIO) import Text.Pandoc.Class.PandocIO (runIO)
import Text.Pandoc.Definition import Text.Pandoc.Definition
import Text.Pandoc.Lua.Filter (SingletonsList (..), walkInlines, import Text.Pandoc.Lua.Filter (SingletonsList (..), LuaFilter, peekLuaFilter,
walkInlineLists, walkBlocks, walkBlockLists) walkInlines, walkInlineLists,
walkBlocks, walkBlockLists)
import Text.Pandoc.Lua.Marshaling () import Text.Pandoc.Lua.Marshaling ()
import Text.Pandoc.Lua.Marshaling.AST import Text.Pandoc.Lua.Marshaling.AST
import Text.Pandoc.Lua.Marshaling.Attr (mkAttr, mkAttributeList) import Text.Pandoc.Lua.Marshaling.Attr (mkAttr, mkAttributeList)
@ -40,13 +43,15 @@ import Text.Pandoc.Lua.Marshaling.List (List (..))
import Text.Pandoc.Lua.Marshaling.ListAttributes ( mkListAttributes import Text.Pandoc.Lua.Marshaling.ListAttributes ( mkListAttributes
, peekListAttributes) , peekListAttributes)
import Text.Pandoc.Lua.Marshaling.SimpleTable (mkSimpleTable) import Text.Pandoc.Lua.Marshaling.SimpleTable (mkSimpleTable)
import Text.Pandoc.Lua.PandocLua (PandocLua, addFunction, liftPandocLua, import Text.Pandoc.Lua.Module.Utils (sha1)
import Text.Pandoc.Lua.PandocLua (PandocLua, liftPandocLua,
loadDefaultModule) loadDefaultModule)
import Text.Pandoc.Options (ReaderOptions (readerExtensions)) import Text.Pandoc.Options (ReaderOptions (readerExtensions))
import Text.Pandoc.Process (pipeProcess) import Text.Pandoc.Process (pipeProcess)
import Text.Pandoc.Readers (Reader (..), getReader) import Text.Pandoc.Readers (Reader (..), getReader)
import Text.Pandoc.Walk (Walkable) import Text.Pandoc.Walk (Walkable)
import qualified HsLua as Lua
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Lazy.Char8 as BSL
import qualified Data.Text as T import qualified Data.Text as T
@ -57,45 +62,74 @@ import Text.Pandoc.Error
-- module to be loadable. -- module to be loadable.
pushModule :: PandocLua NumResults pushModule :: PandocLua NumResults
pushModule = do pushModule = do
liftPandocLua $ Lua.pushModule documentedModule
loadDefaultModule "pandoc" loadDefaultModule "pandoc"
addFunction "read" read let copyNext = do
addFunction "pipe" pipe hasNext <- next (nth 2)
addFunction "walk_block" (walkElement peekBlock pushBlock) if not hasNext
addFunction "walk_inline" (walkElement peekInline pushInline) then return ()
-- Constructors else do
addFunction "Attr" (liftPandocLua mkAttr) pushvalue (nth 2)
addFunction "AttributeList" (liftPandocLua mkAttributeList) insert (nth 2)
addFunction "Pandoc" mkPandoc rawset (nth 5) -- pandoc module
copyNext
liftPandocLua $ do liftPandocLua $ do
let addConstr fn = do pushnil -- initial key
pushName (functionName fn) copyNext
pushDocumentedFunction fn pop 1
rawset (nth 3)
forM_ otherConstructors addConstr
forM_ blockConstructors addConstr
forM_ inlineConstructors addConstr
let addConstructorTable constructors = do
-- add constructors to Inlines.constructor
newtable -- constructor
forM_ constructors $ \fn -> do
let name = functionName fn
pushName name
pushName name
rawget (nth 4)
rawset (nth 3)
-- set as pandoc.Inline.constructor
pushName "Inline"
newtable *> pushName "constructor" *>
pushvalue (nth 4) *> rawset (nth 3)
rawset (nth 4)
pop 1 -- remaining constructor table
addConstructorTable (blockConstructors @PandocError)
addConstructorTable (inlineConstructors @PandocError)
-- Add string constants
forM_ stringConstants $ \c -> do
pushString c *> pushString c *> rawset (nth 3)
return 1 return 1
documentedModule :: Module PandocError
documentedModule = Module
{ moduleName = "pandoc"
, moduleDescription = T.unlines
[ "Lua functions for pandoc scripts; includes constructors for"
, "document elements, functions to parse text in a given"
, "format, and functions to filter and modify a subtree."
]
, moduleFields = stringConstants ++ [inlineField, blockField]
, moduleOperations = []
, moduleFunctions = mconcat
[ functions
, otherConstructors
, blockConstructors
, inlineConstructors
]
}
-- | Inline table field
inlineField :: Field PandocError
inlineField = Field
{ fieldName = "Inline"
, fieldDescription = "Inline constructors, nested under 'constructors'."
-- the nesting happens for historical reasons and should probably be
-- changed.
, fieldPushValue = pushWithConstructorsSubtable inlineConstructors
}
-- | @Block@ module field
blockField :: Field PandocError
blockField = Field
{ fieldName = "Block"
, fieldDescription = "Inline constructors, nested under 'constructors'."
-- the nesting happens for historical reasons and should probably be
-- changed.
, fieldPushValue = 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
inlineConstructors :: LuaError e => [DocumentedFunction e] inlineConstructors :: LuaError e => [DocumentedFunction e]
inlineConstructors = inlineConstructors =
[ defun "Cite" [ defun "Cite"
@ -291,7 +325,13 @@ mkInlinesConstr name constr = defun name
otherConstructors :: LuaError e => [DocumentedFunction e] otherConstructors :: LuaError e => [DocumentedFunction e]
otherConstructors = otherConstructors =
[ defun "Citation" [ defun "Pandoc"
### liftPure2 (\blocks mMeta -> Pandoc (fromMaybe nullMeta mMeta) blocks)
<#> parameter peekBlocksFuzzy "Blocks" "blocks" "document contents"
<#> optionalParameter peekMeta "Meta" "meta" "document metadata"
=#> functionResult pushPandoc "Pandoc" "new Pandoc document"
, defun "Citation"
### (\cid mode mprefix msuffix mnote_num mhash -> ### (\cid mode mprefix msuffix mnote_num mhash ->
cid `seq` mode `seq` mprefix `seq` msuffix `seq` cid `seq` mode `seq` mprefix `seq` msuffix `seq`
mnote_num `seq` mhash `seq` return $! Citation mnote_num `seq` mhash `seq` return $! Citation
@ -311,68 +351,93 @@ otherConstructors =
=#> functionResult pushCitation "Citation" "new citation object" =#> functionResult pushCitation "Citation" "new citation object"
#? "Creates a single citation." #? "Creates a single citation."
, mkAttr
, mkAttributeList
, mkListAttributes , mkListAttributes
, mkSimpleTable , mkSimpleTable
] ]
stringConstants :: [String] stringConstants :: [Field e]
stringConstants = stringConstants =
let constrs :: forall a. Data a => Proxy a -> [String] let constrs :: forall a. Data a => Proxy a -> [String]
constrs _ = map showConstr . dataTypeConstrs . dataTypeOf @a $ undefined constrs _ = map showConstr . dataTypeConstrs . dataTypeOf @a $ undefined
in constrs (Proxy @ListNumberStyle) nullaryConstructors = mconcat
++ constrs (Proxy @ListNumberDelim) [ constrs (Proxy @ListNumberStyle)
++ constrs (Proxy @QuoteType) , constrs (Proxy @ListNumberDelim)
++ constrs (Proxy @MathType) , constrs (Proxy @QuoteType)
++ constrs (Proxy @Alignment) , constrs (Proxy @MathType)
++ constrs (Proxy @CitationMode) , constrs (Proxy @Alignment)
, constrs (Proxy @CitationMode)
]
toField s = Field
{ fieldName = T.pack s
, fieldDescription = T.pack s
, fieldPushValue = pushString s
}
in map toField nullaryConstructors
walkElement :: (Walkable (SingletonsList Inline) a, walkElement :: (Walkable (SingletonsList Inline) a,
Walkable (SingletonsList Block) a, Walkable (SingletonsList Block) a,
Walkable (List Inline) a, Walkable (List Inline) a,
Walkable (List Block) a) Walkable (List Block) a)
=> Peeker PandocError a -> Pusher PandocError a => a -> LuaFilter -> LuaE PandocError a
-> LuaE PandocError NumResults walkElement x f = walkInlines f x
walkElement peek' push' = do >>= walkInlineLists f
x <- forcePeek $ peek' (nthBottom 1) >>= walkBlocks f
f <- peek (nthBottom 2) >>= walkBlockLists f
let walk' = walkInlines f
>=> walkInlineLists f
>=> walkBlocks f
>=> walkBlockLists f
walk' x >>= push'
return (NumResults 1)
read :: T.Text -> Optional T.Text -> PandocLua NumResults functions :: [DocumentedFunction PandocError]
read content formatSpecOrNil = liftPandocLua $ do functions =
let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil) [ defun "pipe"
res <- Lua.liftIO . runIO $ ### (\command args input -> do
getReader formatSpec >>= \(rdr,es) -> (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input
case rdr of `catch` (throwM . PandocIOError "pipe")
TextReader r -> case ec of
r def{ readerExtensions = es } content ExitSuccess -> 1 <$ Lua.pushLazyByteString output
_ -> throwError $ PandocSomeError ExitFailure n -> do
"Only textual formats are supported" pushPipeError (PipeError (T.pack command) n output)
case res of Lua.error)
Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc <#> parameter peekString "string" "command" "path to executable"
Left (PandocUnknownReaderError f) -> Lua.raiseError $ <#> parameter (peekList peekString) "{string,...}" "args"
"Unknown reader: " <> f "list of arguments"
Left (PandocUnsupportedExtensionError e f) -> Lua.raiseError $ <#> parameter peekLazyByteString "string" "input"
"Extension " <> e <> " not supported for " <> f "input passed to process via stdin"
Left e -> Lua.raiseError $ show e =?> "output string, or error triple"
-- | Pipes input through a command. , defun "read"
pipe :: String -- ^ path to executable ### (\content mformatspec -> do
-> [String] -- ^ list of arguments let formatSpec = fromMaybe "markdown" mformatspec
-> BL.ByteString -- ^ input passed to process via stdin res <- Lua.liftIO . runIO $ getReader formatSpec >>= \case
-> PandocLua NumResults (TextReader r, es) -> r def{ readerExtensions = es } content
pipe command args input = liftPandocLua $ do _ -> throwError $ PandocSomeError
(ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input "Only textual formats are supported"
`catch` (throwM . PandocIOError "pipe") case res of
case ec of Right pd -> return pd -- success, got a Pandoc document
ExitSuccess -> 1 <$ Lua.push output Left (PandocUnknownReaderError f) ->
ExitFailure n -> do Lua.failLua . T.unpack $ "Unknown reader: " <> f
pushPipeError (PipeError (T.pack command) n output) Left (PandocUnsupportedExtensionError e f) ->
Lua.error Lua.failLua . T.unpack $
"Extension " <> e <> " not supported for " <> f
Left e ->
throwM e)
<#> parameter peekText "string" "content" "text to parse"
<#> optionalParameter peekText "string" "formatspec" "format and extensions"
=#> functionResult pushPandoc "Pandoc" "result document"
, sha1
, defun "walk_block"
### walkElement
<#> parameter peekBlockFuzzy "Block" "block" "element to traverse"
<#> parameter peekLuaFilter "LuaFilter" "filter" "filter functions"
=#> functionResult pushBlock "Block" "modified Block"
, defun "walk_inline"
### walkElement
<#> parameter peekInlineFuzzy "Inline" "inline" "element to traverse"
<#> parameter peekLuaFilter "LuaFilter" "filter" "filter functions"
=#> functionResult pushInline "Inline" "modified Inline"
]
data PipeError = PipeError data PipeError = PipeError
{ pipeErrorCommand :: T.Text { pipeErrorCommand :: T.Text
@ -416,12 +481,3 @@ pushPipeError pipeErr = do
, if output == mempty then BSL.pack "<no output>" else output , if output == mempty then BSL.pack "<no output>" else output
] ]
return (NumResults 1) return (NumResults 1)
mkPandoc :: PandocLua NumResults
mkPandoc = liftPandocLua $ do
doc <- forcePeek $ do
blks <- peekBlocksFuzzy (nthBottom 1)
mMeta <- optional $ peekMeta (nthBottom 2)
pure $ Pandoc (fromMaybe nullMeta mMeta) blks
pushPandoc doc
return 1

View file

@ -11,34 +11,28 @@
Pandoc's system Lua module. Pandoc's system Lua module.
-} -}
module Text.Pandoc.Lua.Module.System module Text.Pandoc.Lua.Module.System
( pushModule ( documentedModule
) where ) where
import HsLua hiding (pushModule) import HsLua
import HsLua.Module.System import HsLua.Module.System
(arch, env, getwd, os, with_env, with_tmpdir, with_wd) (arch, env, getwd, os, with_env, with_tmpdir, with_wd)
import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.ErrorConversion ()
import qualified HsLua as Lua
-- | Push the pandoc.system module on the Lua stack. -- | Push the pandoc.system module on the Lua stack.
pushModule :: LuaE PandocError NumResults documentedModule :: LuaError e => Module e
pushModule = do documentedModule = Module
Lua.pushModule $ Module { moduleName = "pandoc.system"
{ moduleName = "system" , moduleDescription = "system functions"
, moduleDescription = "system functions" , moduleFields =
, moduleFields = [ arch
[ arch , os
, os ]
] , moduleFunctions =
, moduleFunctions = [ setName "environment" env
[ setName "environment" env , setName "get_working_directory" getwd
, setName "get_working_directory" getwd , setName "with_environment" with_env
, setName "with_environment" with_env , setName "with_temporary_directory" with_tmpdir
, setName "with_temporary_directory" with_tmpdir , setName "with_working_directory" with_wd
, setName "with_working_directory" with_wd ]
] , moduleOperations = []
, moduleOperations = [] }
}
return 1

View file

@ -10,34 +10,52 @@
Pandoc data type constructors. Pandoc data type constructors.
-} -}
module Text.Pandoc.Lua.Module.Types module Text.Pandoc.Lua.Module.Types
( pushModule ( documentedModule
) where ) where
import HsLua (LuaE, NumResults, Peeker, Pusher) import HsLua ( LuaE, NumResults, Peeker, Pusher, Module (..), Field (..)
, defun, functionResult, parameter, (###), (<#>), (=#>))
import HsLua.Module.Version (peekVersionFuzzy, pushVersion)
import Text.Pandoc.Error (PandocError) import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.ErrorConversion () import Text.Pandoc.Lua.ErrorConversion ()
import Text.Pandoc.Lua.Marshaling.AST import Text.Pandoc.Lua.Marshaling.AST
import Text.Pandoc.Lua.Util (addFunction)
import qualified HsLua as Lua import qualified HsLua as Lua
import qualified HsLua.Module.Version as Version
-- | Push the pandoc.types module on the Lua stack. -- | Push the pandoc.types module on the Lua stack.
pushModule :: LuaE PandocError NumResults documentedModule :: Module PandocError
pushModule = do documentedModule = Module
Lua.newtable { moduleName = "pandoc.types"
Lua.pushName "Version" *> Lua.pushModule Version.documentedModule , moduleDescription =
*> Lua.rawset (Lua.nth 3) "Constructors for types that are not part of the pandoc AST."
pushCloneTable , moduleFields =
Lua.setfield (Lua.nth 2) "clone" [ Field
return 1 { fieldName = "clone"
, fieldDescription = "DEPRECATED! Helper functions for element cloning."
pushCloneTable :: LuaE PandocError NumResults , fieldPushValue = do
pushCloneTable = do Lua.newtable
Lua.newtable addFunction "Meta" $ cloneWith peekMeta pushMeta
addFunction "Meta" $ cloneWith peekMeta Lua.push addFunction "MetaValue" $ cloneWith peekMetaValue pushMetaValue
addFunction "MetaValue" $ cloneWith peekMetaValue pushMetaValue }
return 1 ]
, moduleFunctions =
[ defun "Version"
### return
<#> parameter peekVersionFuzzy "string|integer|{integer,...}|Version"
"version_specifier"
(mconcat [ "either a version string like `'2.7.3'`, "
, "a single integer like `2`, "
, "list of integers like `{2,7,3}`, "
, "or a Version object"
])
=#> functionResult pushVersion "Version" "A new Version object."
]
, moduleOperations = []
}
where addFunction name fn = do
Lua.pushName name
Lua.pushHaskellFunction fn
Lua.rawset (Lua.nth 3)
cloneWith :: Peeker PandocError a cloneWith :: Peeker PandocError a
-> Pusher PandocError a -> Pusher PandocError a

View file

@ -13,7 +13,8 @@
Utility module for Lua, exposing internal helper functions. Utility module for Lua, exposing internal helper functions.
-} -}
module Text.Pandoc.Lua.Module.Utils module Text.Pandoc.Lua.Module.Utils
( pushModule ( documentedModule
, sha1
) where ) where
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
@ -21,7 +22,7 @@ import Control.Monad ((<$!>))
import Data.Data (showConstr, toConstr) import Data.Data (showConstr, toConstr)
import Data.Default (def) import Data.Default (def)
import Data.Version (Version) import Data.Version (Version)
import HsLua as Lua hiding (pushModule) import HsLua as Lua
import HsLua.Class.Peekable (PeekError) import HsLua.Class.Peekable (PeekError)
import HsLua.Module.Version (peekVersionFuzzy, pushVersion) import HsLua.Module.Version (peekVersionFuzzy, pushVersion)
import Text.Pandoc.Definition import Text.Pandoc.Definition
@ -39,7 +40,6 @@ import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua))
import qualified Data.Digest.Pure.SHA as SHA import qualified Data.Digest.Pure.SHA as SHA
import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T import qualified Data.Text as T
import qualified HsLua.Packaging as Lua
import qualified Text.Pandoc.Builder as B import qualified Text.Pandoc.Builder as B
import qualified Text.Pandoc.Filter.JSON as JSONFilter import qualified Text.Pandoc.Filter.JSON as JSONFilter
import qualified Text.Pandoc.Shared as Shared import qualified Text.Pandoc.Shared as Shared
@ -47,8 +47,8 @@ import qualified Text.Pandoc.UTF8 as UTF8
import qualified Text.Pandoc.Writers.Shared as Shared import qualified Text.Pandoc.Writers.Shared as Shared
-- | Push the "pandoc.utils" module to the Lua stack. -- | Push the "pandoc.utils" module to the Lua stack.
pandocUtilsModule :: Module PandocError documentedModule :: Module PandocError
pandocUtilsModule = Module documentedModule = Module
{ moduleName = "pandoc.utils" { moduleName = "pandoc.utils"
, moduleDescription = "pandoc utility functions" , moduleDescription = "pandoc utility functions"
, moduleFields = [] , moduleFields = []
@ -92,12 +92,7 @@ pandocUtilsModule = Module
, "Returns nil instead of a string if the conversion failed." , "Returns nil instead of a string if the conversion failed."
] ]
, defun "sha1" , sha1
### liftPure (SHA.showDigest . SHA.sha1)
<#> parameter (fmap BSL.fromStrict . peekByteString) "string"
"input" ""
=#> functionResult pushString "string" "hexadecimal hash value"
#? "Compute the hash of the given string value."
, defun "Version" , defun "Version"
### liftPure (id @Version) ### liftPure (id @Version)
@ -146,8 +141,13 @@ pandocUtilsModule = Module
] ]
} }
pushModule :: LuaE PandocError NumResults -- | Documented Lua function to compute the hash of a string.
pushModule = 1 <$ Lua.pushModule pandocUtilsModule sha1 :: DocumentedFunction e
sha1 = defun "sha1"
### liftPure (SHA.showDigest . SHA.sha1)
<#> parameter (fmap BSL.fromStrict . peekByteString) "string" "input" ""
=#> functionResult pushString "string" "hexadecimal hash value"
#? "Compute the hash of the given string value."
-- | Convert pandoc structure to a string with formatting removed. -- | Convert pandoc structure to a string with formatting removed.

View file

@ -16,7 +16,6 @@ module Text.Pandoc.Lua.Packages
) where ) where
import Control.Monad (forM_) import Control.Monad (forM_)
import HsLua (NumResults)
import Text.Pandoc.Error (PandocError) import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.PandocLua (PandocLua, liftPandocLua, loadDefaultModule) import Text.Pandoc.Lua.PandocLua (PandocLua, liftPandocLua, loadDefaultModule)
@ -43,24 +42,27 @@ installPandocPackageSearcher = liftPandocLua $ do
Lua.rawseti (-2) (i + 1) Lua.rawseti (-2) (i + 1)
-- | Load a pandoc module. -- | Load a pandoc module.
pandocPackageSearcher :: String -> PandocLua NumResults pandocPackageSearcher :: String -> PandocLua Lua.NumResults
pandocPackageSearcher pkgName = pandocPackageSearcher pkgName =
case pkgName of case pkgName of
"pandoc" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError Pandoc.pushModule "pandoc" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError Pandoc.pushModule
"pandoc.mediabag" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError MediaBag.pushModule "pandoc.mediabag" -> pushModuleLoader MediaBag.documentedModule
"pandoc.path" -> pushWrappedHsFun "pandoc.path" -> pushModuleLoader Path.documentedModule
(Lua.NumResults 1 <$ Lua.pushModule @PandocError Path.documentedModule) "pandoc.system" -> pushModuleLoader System.documentedModule
"pandoc.system" -> pushWrappedHsFun $ Lua.toHaskellFunction System.pushModule "pandoc.types" -> pushModuleLoader Types.documentedModule
"pandoc.types" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError Types.pushModule "pandoc.utils" -> pushModuleLoader Utils.documentedModule
"pandoc.utils" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError Utils.pushModule "text" -> pushModuleLoader Text.documentedModule
"text" -> pushWrappedHsFun "pandoc.List" -> pushWrappedHsFun . Lua.toHaskellFunction @PandocError $
(Lua.NumResults 1 <$ Lua.pushModule @PandocError Text.documentedModule) loadDefaultModule pkgName
"pandoc.List" -> pushWrappedHsFun $ Lua.toHaskellFunction @PandocError (loadDefaultModule pkgName)
_ -> reportPandocSearcherFailure _ -> reportPandocSearcherFailure
where where
pushModuleLoader mdl = liftPandocLua $ do
Lua.pushHaskellFunction $
Lua.NumResults 1 <$ Lua.pushModule @PandocError mdl
return (Lua.NumResults 1)
pushWrappedHsFun f = liftPandocLua $ do pushWrappedHsFun f = liftPandocLua $ do
Lua.pushHaskellFunction f Lua.pushHaskellFunction f
return 1 return 1
reportPandocSearcherFailure = liftPandocLua $ do reportPandocSearcherFailure = liftPandocLua $ do
Lua.push ("\n\t" <> pkgName <> "is not one of pandoc's default packages") Lua.push ("\n\t" <> pkgName <> "is not one of pandoc's default packages")
return (1 :: NumResults) return (Lua.NumResults 1)

View file

@ -22,7 +22,6 @@ module Text.Pandoc.Lua.PandocLua
( PandocLua (..) ( PandocLua (..)
, runPandocLua , runPandocLua
, liftPandocLua , liftPandocLua
, addFunction
, loadDefaultModule , loadDefaultModule
) where ) where
@ -76,13 +75,6 @@ instance {-# OVERLAPPING #-} Exposable PandocError (PandocLua NumResults) where
instance Pushable a => Exposable PandocError (PandocLua a) where instance Pushable a => Exposable PandocError (PandocLua a) where
partialApply _narg x = 1 <$ (unPandocLua x >>= Lua.push) partialApply _narg x = 1 <$ (unPandocLua x >>= Lua.push)
-- | Add a function to the table at the top of the stack, using the given name.
addFunction :: Exposable PandocError a => Name -> a -> PandocLua ()
addFunction name fn = liftPandocLua $ do
Lua.pushName name
Lua.pushHaskellFunction $ toHaskellFunction fn
Lua.rawset (-3)
-- | Load a pure Lua module included with pandoc. Leaves the result on -- | Load a pure Lua module included with pandoc. Leaves the result on
-- the stack and returns @NumResults 1@. -- the stack and returns @NumResults 1@.
-- --

View file

@ -18,7 +18,6 @@ Lua utility functions.
module Text.Pandoc.Lua.Util module Text.Pandoc.Lua.Util
( getTag ( getTag
, addField , addField
, addFunction
, callWithTraceback , callWithTraceback
, dofileWithTraceback , dofileWithTraceback
, pushViaConstr' , pushViaConstr'
@ -35,14 +34,6 @@ addField key value = do
Lua.push value Lua.push value
Lua.rawset (Lua.nth 3) Lua.rawset (Lua.nth 3)
-- | Add a function to the table at the top of the stack, using the
-- given name.
addFunction :: Exposable e a => String -> a -> LuaE e ()
addFunction name fn = do
Lua.push name
Lua.pushHaskellFunction $ toHaskellFunction fn
Lua.rawset (-3)
-- | Get the tag of a value. This is an optimized and specialized version of -- | Get the tag of a value. This is an optimized and specialized version of
-- @Lua.getfield idx "tag"@. It only checks for the field on the table at index -- @Lua.getfield idx "tag"@. It only checks for the field on the table at index
-- @idx@ and on its metatable, also ignoring any @__index@ value on the -- @idx@ and on its metatable, also ignoring any @__index@ value on the

View file

@ -408,6 +408,12 @@ return {
}) })
assert.are_same(expected, pandoc.read(valid_markdown)) assert.are_same(expected, pandoc.read(valid_markdown))
end), end),
test('unsupported extension', function ()
assert.error_matches(
function () pandoc.read('foo', 'gfm+empty_paragraphs') end,
'Extension empty_paragraphs not supported for gfm'
)
end),
test('failing read', function () test('failing read', function ()
assert.error_matches( assert.error_matches(
function () pandoc.read('foo', 'nosuchreader') end, function () pandoc.read('foo', 'nosuchreader') end,