Lua: marshal Block values as userdata objects

Properties of Block values are marshalled lazily, which generally
improves performance considerably. Script users may also notice the
following differences:

  - Block element properties can no longer be accessed by numerical
    indexing of the `.c` field. The `.c` property now serves as an alias
    for `.content`, so some filter that used this undocumented method
    for property access may continue to work, while others will need to
    be updated and use proper property names.

  - The marshalled Block elements now have a `show` method, and a
    `__tostring` metamethod. Both return the Haskell string
    representation of the element.

  - Block values now have the Lua type `userdata` instead of `table`.
This commit is contained in:
Albert Krewinkel 2021-10-26 14:40:10 +02:00
parent 230b133db5
commit a493c7029c
No known key found for this signature in database
GPG key ID: 388DC0B21F631124
6 changed files with 582 additions and 352 deletions

View file

@ -273,22 +273,6 @@ local function ensureInlineList (x)
end
end
--- Ensure that the given object is a definition pair, convert if necessary.
-- @local
local function ensureDefinitionPairs (pair)
local inlines = ensureInlineList(pair[1] or {})
local blocks = ensureList(pair[2] or {}):map(ensureList)
return {inlines, blocks}
end
--- Try hard to turn the arguments into an Attr object.
local function ensureAttr(attr)
if type(attr) == 'userdata' then
return attr
end
return M.Attr(attr)
end
------------------------------------------------------------------------
-- Meta
-- @section Meta
@ -364,199 +348,10 @@ function M.MetaBool(bool)
return bool
end
------------------------------------------------------------------------
-- Blocks
-- @section Block
--- Block elements
M.Block = AstElement:make_subtype'Block'
M.Block.behavior.clone = M.types.clone.Block
--- Creates a block quote element
-- @function BlockQuote
-- @tparam {Block,...} content block content
-- @treturn Block block quote element
M.BlockQuote = M.Block:create_constructor(
"BlockQuote",
function(content) return {c = ensureList(content)} end,
"content"
)
--- Creates a bullet (i.e. unordered) list.
-- @function BulletList
-- @tparam {{Block,...},...} content list of items
-- @treturn Block bullet list element
M.BulletList = M.Block:create_constructor(
"BulletList",
function(content) return {c = ensureList(content):map(ensureList)} end,
"content"
)
--- Creates a code block element
-- @function CodeBlock
-- @tparam string text code string
-- @tparam[opt] Attr attr element attributes
-- @treturn Block code block element
M.CodeBlock = M.Block:create_constructor(
"CodeBlock",
function(text, attr) return {c = {ensureAttr(attr), text}} end,
{{attr = {"identifier", "classes", "attributes"}}, "text"}
)
--- Creates a definition list, containing terms and their explanation.
-- @function DefinitionList
-- @tparam {{{Inline,...},{{Block,...}}},...} content list of items
-- @treturn Block definition list element
M.DefinitionList = M.Block:create_constructor(
"DefinitionList",
function(content)
return {c = ensureList(content):map(ensureDefinitionPairs)}
end,
"content"
)
--- Creates a div element
-- @function Div
-- @tparam {Block,...} content block content
-- @tparam[opt] Attr attr element attributes
-- @treturn Block div element
M.Div = M.Block:create_constructor(
"Div",
function(content, attr)
return {c = {ensureAttr(attr), ensureList(content)}}
end,
{{attr = {"identifier", "classes", "attributes"}}, "content"}
)
--- Creates a header element.
-- @function Header
-- @tparam int level header level
-- @tparam {Inline,...} content inline content
-- @tparam[opt] Attr attr element attributes
-- @treturn Block header element
M.Header = M.Block:create_constructor(
"Header",
function(level, content, attr)
return {c = {level, ensureAttr(attr), ensureInlineList(content)}}
end,
{"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
)
--- Creates a horizontal rule.
-- @function HorizontalRule
-- @treturn Block horizontal rule
M.HorizontalRule = M.Block:create_constructor(
"HorizontalRule",
function() return {} end
)
--- Creates a line block element.
-- @function LineBlock
-- @tparam {{Inline,...},...} content inline content
-- @treturn Block line block element
M.LineBlock = M.Block:create_constructor(
"LineBlock",
function(content) return {c = ensureList(content):map(ensureInlineList)} end,
"content"
)
--- Creates a null element.
-- @function Null
-- @treturn Block null element
M.Null = M.Block:create_constructor(
"Null",
function() return {} end
)
--- Creates an ordered list.
-- @function OrderedList
-- @tparam {{Block,...},...} items list items
-- @param[opt] listAttributes list parameters
-- @treturn Block ordered list element
M.OrderedList = M.Block:create_constructor(
"OrderedList",
function(items, listAttributes)
listAttributes = listAttributes or M.ListAttributes()
return {c = {listAttributes, ensureList(items):map(ensureList)}}
end,
{{listAttributes = {"start", "style", "delimiter"}}, "content"}
)
--- Creates a para element.
-- @function Para
-- @tparam {Inline,...} content inline content
-- @treturn Block paragraph element
M.Para = M.Block:create_constructor(
"Para",
function(content) return {c = ensureInlineList(content)} end,
"content"
)
--- Creates a plain element.
-- @function Plain
-- @tparam {Inline,...} content inline content
-- @treturn Block plain element
M.Plain = M.Block:create_constructor(
"Plain",
function(content) return {c = ensureInlineList(content)} end,
"content"
)
--- Creates a raw content block of the specified format.
-- @function RawBlock
-- @tparam string format format of content
-- @tparam string text string content
-- @treturn Block raw block element
M.RawBlock = M.Block:create_constructor(
"RawBlock",
function(format, text) return {c = {format, text}} end,
{"format", "text"}
)
--- Creates a table element.
-- @function Table
-- @tparam Caption caption table caption
-- @tparam {ColSpec,...} colspecs column alignments and widths
-- @tparam TableHead head table head
-- @tparam {TableBody,..} bodies table bodies
-- @treturn TableFoot foot table foot
-- @tparam[opt] Attr attr attributes
M.Table = M.Block:create_constructor(
"Table",
function(caption, colspecs, head, bodies, foot, attr)
return {
c = {
ensureAttr(attr),
caption,
List:new(colspecs),
head,
List:new(bodies),
foot
}
}
end,
{"attr", "caption", "colspecs", "head", "bodies", "foot"}
)
------------------------------------------------------------------------
-- Element components
-- @section components
-- Monkey-patch setters for `attr` fields to be more forgiving in the input that
-- results in a valid Attr value.
function augment_attr_setter (setters)
if setters.attr then
local orig = setters.attr
setters.attr = function(k, v)
orig(k, ensureAttr(v))
end
end
end
for _, blk in pairs(M.Block.constructor) do
augment_attr_setter(blk.behavior.setters)
end
-- ListAttributes
M.ListAttributes = AstElement:make_subtype 'ListAttributes'
M.ListAttributes.behavior.clone = M.types.clone.ListAttributes

View file

@ -83,12 +83,8 @@ initLuaState = do
-- stack.
putConstructorsInRegistry :: PandocLua ()
putConstructorsInRegistry = liftPandocLua $ do
constrsToReg $ Pandoc.Pandoc mempty mempty
constrsToReg $ Pandoc.Str mempty
constrsToReg $ Pandoc.Para mempty
constrsToReg $ Pandoc.Meta mempty
constrsToReg $ Pandoc.MetaList mempty
constrsToReg $ Pandoc.Citation mempty mempty mempty Pandoc.AuthorInText 0 0
putInReg "ListAttributes" -- used for ListAttributes type alias
putInReg "List" -- pandoc.List
putInReg "SimpleTable" -- helper for backward-compatible table handling

View file

@ -19,21 +19,27 @@ Marshaling/unmarshaling instances for document AST elements.
module Text.Pandoc.Lua.Marshaling.AST
( peekAttr
, peekBlock
, peekBlockFuzzy
, peekBlocks
, peekBlocksFuzzy
, peekCaption
, peekCitation
, peekColSpec
, peekDefinitionItem
, peekFormat
, peekInline
, peekInlineFuzzy
, peekInlines
, peekInlinesFuzzy
, peekListAttributes
, peekMeta
, peekMetaValue
, peekPandoc
, peekMathType
, peekQuoteType
, peekFuzzyInlines
, peekFuzzyBlocks
, peekTableBody
, peekTableHead
, peekTableFoot
, pushAttr
, pushBlock
@ -46,7 +52,7 @@ module Text.Pandoc.Lua.Marshaling.AST
import Control.Applicative ((<|>), optional)
import Control.Monad.Catch (throwM)
import Control.Monad ((<$!>), (>=>))
import Control.Monad ((<$!>))
import Data.Data (showConstr, toConstr)
import Data.Text (Text)
import Data.Version (Version)
@ -54,7 +60,7 @@ import HsLua hiding (Operation (Div))
import HsLua.Module.Version (peekVersionFuzzy)
import Text.Pandoc.Definition
import Text.Pandoc.Error (PandocError (PandocLuaError))
import Text.Pandoc.Lua.Util (pushViaConstr', pushViaConstructor)
import Text.Pandoc.Lua.Util (pushViaConstr')
import Text.Pandoc.Lua.Marshaling.Attr (peekAttr, pushAttr)
import Text.Pandoc.Lua.Marshaling.List (pushPandocList)
@ -102,14 +108,6 @@ instance Pushable MetaValue where
instance Pushable Block where
push = pushBlock
-- Inline
instance Pushable Inline where
push = pushInline
-- Citation
instance Pushable Citation where
push = pushCitation
typeCitation :: LuaError e => DocumentedType e Citation
typeCitation = deftype "Citation" []
[ property "id" "citation ID / key"
@ -232,69 +230,188 @@ peekMetaValue = retrieving "MetaValue $ " . \idx -> do
Nothing -> peekUntagged
_ -> failPeek "could not get meta value"
typeBlock :: LuaError e => DocumentedType e Block
typeBlock = deftype "Block"
[ operation Eq $ lambda
### liftPure2 (==)
<#> parameter peekBlockFuzzy "Block" "a" ""
<#> parameter peekBlockFuzzy "Block" "b" ""
=#> boolResult "whether the two values are equal"
, operation Tostring $ lambda
### liftPure show
<#> udparam typeBlock "self" ""
=#> functionResult pushString "string" "Haskell representation"
]
[ possibleProperty "attr" "element attributes"
(pushAttr, \case
CodeBlock attr _ -> Actual attr
Div attr _ -> Actual attr
Header _ attr _ -> Actual attr
Table attr _ _ _ _ _ -> Actual attr
_ -> Absent)
(peekAttr, \case
CodeBlock _ code -> Actual . flip CodeBlock code
Div _ blks -> Actual . flip Div blks
Header lvl _ blks -> Actual . (\attr -> Header lvl attr blks)
Table _ c cs h bs f -> Actual . (\attr -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "bodies" "table bodies"
(pushPandocList pushTableBody, \case
Table _ _ _ _ bs _ -> Actual bs
_ -> Absent)
(peekList peekTableBody, \case
Table attr c cs h _ f -> Actual . (\bs -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "caption" "element caption"
(pushCaption, \case {Table _ capt _ _ _ _ -> Actual capt; _ -> Absent})
(peekCaption, \case
Table attr _ cs h bs f -> Actual . (\c -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "colspecs" "column alignments and widths"
(pushPandocList pushColSpec, \case
Table _ _ cs _ _ _ -> Actual cs
_ -> Absent)
(peekList peekColSpec, \case
Table attr c _ h bs f -> Actual . (\cs -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "content" "element content"
(pushContent, getBlockContent)
(peekContent, setBlockContent)
, possibleProperty "foot" "table foot"
(pushTableFoot, \case {Table _ _ _ _ _ f -> Actual f; _ -> Absent})
(peekTableFoot, \case
Table attr c cs h bs _ -> Actual . (\f -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "format" "format of raw content"
(pushFormat, \case {RawBlock f _ -> Actual f; _ -> Absent})
(peekFormat, \case
RawBlock _ txt -> Actual . (`RawBlock` txt)
_ -> const Absent)
, possibleProperty "head" "table head"
(pushTableHead, \case {Table _ _ _ h _ _ -> Actual h; _ -> Absent})
(peekTableHead, \case
Table attr c cs _ bs f -> Actual . (\h -> Table attr c cs h bs f)
_ -> const Absent)
, possibleProperty "level" "heading level"
(pushIntegral, \case {Header lvl _ _ -> Actual lvl; _ -> Absent})
(peekIntegral, \case
Header _ attr inlns -> Actual . \lvl -> Header lvl attr inlns
_ -> const Absent)
, possibleProperty "listAttributes" "ordered list attributes"
(pushListAttributes, \case
OrderedList listAttr _ -> Actual listAttr
_ -> Absent)
(peekListAttributes, \case
OrderedList _ content -> Actual . (`OrderedList` content)
_ -> const Absent)
, possibleProperty "text" "text contents"
(pushText, getBlockText)
(peekText, setBlockText)
, readonly "tag" "type of Block"
(pushString, showConstr . toConstr )
, alias "t" "tag" ["tag"]
, alias "c" "content" ["content"]
, alias "identifier" "element identifier" ["attr", "identifier"]
, alias "classes" "element classes" ["attr", "classes"]
, alias "attributes" "other element attributes" ["attr", "attributes"]
, alias "start" "ordered list start number" ["listAttributes", "start"]
, alias "style" "ordered list style" ["listAttributes", "style"]
, alias "delimiter" "numbering delimiter" ["listAttributes", "delimiter"]
, method $ defun "clone"
### return
<#> parameter peekBlock "Block" "block" "self"
=#> functionResult pushBlock "Block" "cloned Block"
, method $ defun "show"
### liftPure show
<#> parameter peekBlock "Block" "self" ""
=#> functionResult pushString "string" "Haskell string representation"
]
where
boolResult = functionResult pushBool "boolean"
getBlockContent :: Block -> Possible Content
getBlockContent = \case
-- inline content
Para inlns -> Actual $ ContentInlines inlns
Plain inlns -> Actual $ ContentInlines inlns
-- inline content
BlockQuote blks -> Actual $ ContentBlocks blks
Div _ blks -> Actual $ ContentBlocks blks
-- lines content
LineBlock lns -> Actual $ ContentLines lns
-- list items content
BulletList itms -> Actual $ ContentListItems itms
OrderedList _ itms -> Actual $ ContentListItems itms
-- definition items content
DefinitionList itms -> Actual $ ContentDefItems itms
_ -> Absent
setBlockContent :: Block -> Content -> Possible Block
setBlockContent = \case
-- inline content
Para _ -> Actual . Para . inlineContent
Plain _ -> Actual . Plain . inlineContent
-- block content
BlockQuote _ -> Actual . BlockQuote . blockContent
Div attr _ -> Actual . Div attr . blockContent
-- lines content
LineBlock _ -> Actual . LineBlock . lineContent
-- list items content
BulletList _ -> Actual . BulletList . listItemContent
OrderedList la _ -> Actual . OrderedList la . listItemContent
-- definition items content
DefinitionList _ -> Actual . DefinitionList . defItemContent
_ -> const Absent
where
inlineContent = \case
ContentInlines inlns -> inlns
c -> throwM . PandocLuaError $ "expected Inlines, got " <>
contentTypeDescription c
blockContent = \case
ContentBlocks blks -> blks
ContentInlines inlns -> [Plain inlns]
c -> throwM . PandocLuaError $ "expected Blocks, got " <>
contentTypeDescription c
lineContent = \case
ContentLines lns -> lns
c -> throwM . PandocLuaError $ "expected list of lines, got " <>
contentTypeDescription c
defItemContent = \case
ContentDefItems itms -> itms
c -> throwM . PandocLuaError $ "expected definition items, got " <>
contentTypeDescription c
listItemContent = \case
ContentBlocks blks -> [blks]
ContentLines lns -> map ((:[]) . Plain) lns
ContentListItems itms -> itms
c -> throwM . PandocLuaError $ "expected list of items, got " <>
contentTypeDescription c
getBlockText :: Block -> Possible Text
getBlockText = \case
CodeBlock _ lst -> Actual lst
RawBlock _ raw -> Actual raw
_ -> Absent
setBlockText :: Block -> Text -> Possible Block
setBlockText = \case
CodeBlock attr _ -> Actual . CodeBlock attr
RawBlock f _ -> Actual . RawBlock f
_ -> const Absent
-- | Push a block element to the top of the Lua stack.
pushBlock :: forall e. LuaError e => Block -> LuaE e ()
pushBlock = \case
BlockQuote blcks -> pushViaConstructor @e "BlockQuote" blcks
BulletList items -> pushViaConstructor @e "BulletList" items
CodeBlock attr code -> pushViaConstr' @e "CodeBlock"
[ push code, pushAttr attr ]
DefinitionList items -> pushViaConstructor @e "DefinitionList" items
Div attr blcks -> pushViaConstr' @e "Div"
[push blcks, pushAttr attr]
Header lvl attr inlns -> pushViaConstr' @e "Header"
[push lvl, push inlns, pushAttr attr]
HorizontalRule -> pushViaConstructor @e "HorizontalRule"
LineBlock blcks -> pushViaConstructor @e "LineBlock" blcks
OrderedList lstAttr list -> pushViaConstr' @e "OrderedList"
[ push list, pushListAttributes @e lstAttr ]
Null -> pushViaConstructor @e "Null"
Para blcks -> pushViaConstructor @e "Para" blcks
Plain blcks -> pushViaConstructor @e "Plain" blcks
RawBlock f cs -> pushViaConstructor @e "RawBlock" f cs
Table attr blkCapt specs thead tbody tfoot ->
pushViaConstr' @e "Table"
[ pushCaption blkCapt, push specs, push thead, push tbody
, push tfoot, pushAttr attr]
pushBlock = pushUD typeBlock
-- | Return the value at the given index as block if possible.
peekBlock :: forall e. LuaError e => Peeker e Block
peekBlock = fmap (retrieving "Block")
. typeChecked "table" Lua.istable
$ \idx -> do
-- Get the contents of an AST element.
let mkBlock :: (a -> Block) -> Peeker e a -> Peek e Block
mkBlock f p = f <$!> peekFieldRaw p "c" idx
LuaUtil.getTag idx >>= \case
"BlockQuote" -> mkBlock BlockQuote peekBlocks
"BulletList" -> mkBlock BulletList (peekList peekBlocks)
"CodeBlock" -> mkBlock (uncurry CodeBlock)
(peekPair peekAttr peekText)
"DefinitionList" -> mkBlock DefinitionList
(peekList (peekPair peekInlines (peekList peekBlocks)))
"Div" -> mkBlock (uncurry Div) (peekPair peekAttr peekBlocks)
"Header" -> mkBlock (\(lvl, attr, lst) -> Header lvl attr lst)
(peekTriple peekIntegral peekAttr peekInlines)
"HorizontalRule" -> return HorizontalRule
"LineBlock" -> mkBlock LineBlock (peekList peekInlines)
"OrderedList" -> mkBlock (uncurry OrderedList)
(peekPair peekListAttributes (peekList peekBlocks))
"Null" -> return Null
"Para" -> mkBlock Para peekInlines
"Plain" -> mkBlock Plain peekInlines
"RawBlock" -> mkBlock (uncurry RawBlock)
(peekPair peekFormat peekText)
"Table" -> mkBlock id
(retrieving "Table" . (liftLua . absindex >=> (\idx' -> cleanup $ do
attr <- liftLua (rawgeti idx' 1) *> peekAttr top
capt <- liftLua (rawgeti idx' 2) *> peekCaption top
cs <- liftLua (rawgeti idx' 3) *> peekList peekColSpec top
thead <- liftLua (rawgeti idx' 4) *> peekTableHead top
tbods <- liftLua (rawgeti idx' 5) *> peekList peekTableBody top
tfoot <- liftLua (rawgeti idx' 6) *> peekTableFoot top
return $! Table attr capt cs thead tbods tfoot)))
Name tag -> failPeek ("Unknown block type: " <> tag)
peekBlock = retrieving "Block" . peekUD typeBlock
-- | Retrieves a list of Block elements.
peekBlocks :: LuaError e => Peeker e [Block]
peekBlocks = peekList peekBlock
@ -304,6 +421,16 @@ peekInlines = peekList peekInline
pushInlines :: LuaError e => Pusher e [Inline]
pushInlines = pushPandocList pushInline
-- | Retrieves a single definition item from a the stack; it is expected
-- to be a pair of a list of inlines and a list of list of blocks. Uses
-- fuzzy parsing, i.e., tries hard to convert mismatching types into the
-- expected result.
peekDefinitionItem :: LuaError e => Peeker e ([Inline], [[Block]])
peekDefinitionItem = peekPair peekInlinesFuzzy $ choice
[ peekList peekBlocksFuzzy
, \idx -> (:[]) <$!> peekBlocksFuzzy idx
]
-- | Push Caption element
pushCaption :: LuaError e => Caption -> LuaE e ()
pushCaption (Caption shortCaption longCaption) = do
@ -318,37 +445,48 @@ peekCaption = retrieving "Caption" . \idx -> do
long <- peekFieldRaw peekBlocks "long" idx
return $! Caption short long
-- | Push a ColSpec value as a pair of Alignment and ColWidth.
pushColSpec :: LuaError e => Pusher e ColSpec
pushColSpec = pushPair (pushString . show) pushColWidth
-- | Peek a ColSpec value as a pair of Alignment and ColWidth.
peekColSpec :: LuaError e => Peeker e ColSpec
peekColSpec = peekPair peekRead peekColWidth
peekColWidth :: LuaError e => Peeker e ColWidth
peekColWidth = retrieving "ColWidth" . \idx -> do
maybe ColWidthDefault ColWidth <$!> optional (peekRealFloat idx)
peekColSpec :: LuaError e => Peeker e ColSpec
peekColSpec = peekPair peekRead peekColWidth
instance Pushable ColWidth where
push = \case
-- | Push a ColWidth value by pushing the width as a plain number, or
-- @nil@ for ColWidthDefault.
pushColWidth :: LuaError e => Pusher e ColWidth
pushColWidth = \case
(ColWidth w) -> Lua.push w
ColWidthDefault -> Lua.pushnil
instance Pushable Row where
push (Row attr cells) = Lua.push (attr, cells)
instance Peekable Row where
peek = forcePeek . peekRow
-- | Push a table row as a pair of attr and the list of cells.
pushRow :: LuaError e => Pusher e Row
pushRow (Row attr cells) =
pushPair pushAttr (pushPandocList pushCell) (attr, cells)
-- | Push a table row from a pair of attr and the list of cells.
peekRow :: LuaError e => Peeker e Row
peekRow = ((uncurry Row) <$!>)
. retrieving "Row"
. peekPair peekAttr (peekList peekCell)
instance Pushable TableBody where
push (TableBody attr (RowHeadColumns rowHeadColumns) head' body) = do
-- | Pushes a 'TableBody' value as a Lua table with fields @attr@,
-- @row_head_columns@, @head@, and @body@.
pushTableBody :: LuaError e => Pusher e TableBody
pushTableBody (TableBody attr (RowHeadColumns rowHeadColumns) head' body) = do
Lua.newtable
LuaUtil.addField "attr" attr
LuaUtil.addField "row_head_columns" rowHeadColumns
LuaUtil.addField "head" head'
LuaUtil.addField "body" body
-- | Retrieves a 'TableBody' value from a Lua table with fields @attr@,
-- @row_head_columns@, @head@, and @body@.
peekTableBody :: LuaError e => Peeker e TableBody
peekTableBody = fmap (retrieving "TableBody")
. typeChecked "table" Lua.istable
@ -358,17 +496,25 @@ peekTableBody = fmap (retrieving "TableBody")
<*> peekFieldRaw (peekList peekRow) "head" idx
<*> peekFieldRaw (peekList peekRow) "body" idx
instance Pushable TableHead where
push (TableHead attr rows) = Lua.push (attr, rows)
-- | Push a table head value as the pair of its Attr and rows.
pushTableHead :: LuaError e => Pusher e TableHead
pushTableHead (TableHead attr rows) =
pushPair pushAttr (pushPandocList pushRow) (attr, rows)
-- | Peek a table head value from a pair of Attr and rows.
peekTableHead :: LuaError e => Peeker e TableHead
peekTableHead = ((uncurry TableHead) <$!>)
. retrieving "TableHead"
. peekPair peekAttr (peekList peekRow)
instance Pushable TableFoot where
push (TableFoot attr cells) = Lua.push (attr, cells)
-- | Pushes a 'TableFoot' value as a pair of the Attr value and the list
-- of table rows.
pushTableFoot :: LuaError e => Pusher e TableFoot
pushTableFoot (TableFoot attr rows) =
pushPair pushAttr (pushPandocList pushRow) (attr, rows)
-- | Retrieves a 'TableFoot' value from a pair containing an Attr value
-- and a list of table rows.
peekTableFoot :: LuaError e => Peeker e TableFoot
peekTableFoot = ((uncurry TableFoot) <$!>)
. retrieving "TableFoot"
@ -380,6 +526,8 @@ instance Pushable Cell where
instance Peekable Cell where
peek = forcePeek . peekCell
-- | Push a table cell as a table with fields @attr@, @alignment@,
-- @row_span@, @col_span@, and @contents@.
pushCell :: LuaError e => Cell -> LuaE e ()
pushCell (Cell attr align (RowSpan rowSpan) (ColSpan colSpan) contents) = do
Lua.newtable
@ -416,9 +564,42 @@ setInlineText = \case
Str _ -> Actual . Str
_ -> const Absent
-- | Helper type to represent all the different types a `content`
-- attribute can have.
data Content
= ContentBlocks [Block]
| ContentInlines [Inline]
| ContentLines [[Inline]]
| ContentDefItems [([Inline], [[Block]])]
| ContentListItems [[Block]]
contentTypeDescription :: Content -> Text
contentTypeDescription = \case
ContentBlocks {} -> "list of Block items"
ContentInlines {} -> "list of Inline items"
ContentLines {} -> "list of Inline lists (i.e., a list of lines)"
ContentDefItems {} -> "list of definition items items"
ContentListItems {} -> "list items (i.e., list of list of Block elements)"
pushContent :: LuaError e => Pusher e Content
pushContent = \case
ContentBlocks blks -> pushPandocList pushBlock blks
ContentInlines inlns -> pushPandocList pushInline inlns
ContentLines lns -> pushPandocList (pushPandocList pushInline) lns
ContentDefItems itms ->
let pushItem = pushPair (pushPandocList pushInline)
(pushPandocList (pushPandocList pushBlock))
in pushPandocList pushItem itms
ContentListItems itms ->
pushPandocList (pushPandocList pushBlock) itms
peekContent :: LuaError e => Peeker e Content
peekContent idx =
(ContentInlines <$!> peekInlinesFuzzy idx) <|>
(ContentLines <$!> peekList (peekList peekInlineFuzzy) idx) <|>
(ContentBlocks <$!> peekBlocksFuzzy idx ) <|>
(ContentListItems <$!> peekList peekBlocksFuzzy idx) <|>
(ContentDefItems <$!> peekList (peekDefinitionItem) idx)
setInlineContent :: Inline -> Content -> Possible Inline
setInlineContent = \case
@ -438,13 +619,13 @@ setInlineContent = \case
where
inlineContent = \case
ContentInlines inlns -> inlns
ContentBlocks _ -> throwM $
PandocLuaError "expected Inlines, got Blocks"
c -> throwM . PandocLuaError $ "expected Inlines, got " <>
contentTypeDescription c
blockContent = \case
ContentBlocks blks -> blks
ContentInlines [] -> []
ContentInlines _ -> throwM $
PandocLuaError "expected Blocks, got Inlines"
c -> throwM . PandocLuaError $ "expected Blocks, got " <>
contentTypeDescription c
getInlineContent :: Inline -> Possible Content
getInlineContent = \case
@ -496,16 +677,6 @@ showInline = defun "show"
<#> parameter peekInline "inline" "Inline" "Object"
=#> functionResult pushString "string" "stringified Inline"
pushContent :: LuaError e => Pusher e Content
pushContent = \case
ContentBlocks blks -> pushPandocList pushBlock blks
ContentInlines inlns -> pushPandocList pushInline inlns
peekContent :: LuaError e => Peeker e Content
peekContent idx =
(ContentInlines <$!> peekList peekInline idx) <|>
(ContentBlocks <$!> peekList peekBlock idx)
typeInline :: LuaError e => DocumentedType e Inline
typeInline = deftype "Inline"
[ operation Tostring showInline
@ -591,22 +762,37 @@ pushInline = pushUD typeInline
peekInline :: forall e. LuaError e => Peeker e Inline
peekInline = retrieving "Inline" . \idx -> peekUD typeInline idx
-- | Try extra hard to retrieve an Inline value from the stack. Treats
-- bare strings as @Str@ values.
peekInlineFuzzy :: LuaError e => Peeker e Inline
peekInlineFuzzy = retrieving "Inline" . choice
[ peekUD typeInline
, \idx -> Str <$!> peekText idx
]
-- | Try extra-hard to return the value at the given index as a list of
-- inlines.
peekFuzzyInlines :: LuaError e => Peeker e [Inline]
peekFuzzyInlines = choice
[ peekList peekInline
, fmap pure . peekInline
, \idx -> pure . Str <$!> peekText idx
peekInlinesFuzzy :: LuaError e => Peeker e [Inline]
peekInlinesFuzzy = choice
[ peekList peekInlineFuzzy
, fmap pure . peekInlineFuzzy
]
peekFuzzyBlocks :: LuaError e => Peeker e [Block]
peekFuzzyBlocks = choice
[ peekList peekBlock
, fmap pure . peekBlock
, \idx -> pure . Plain . pure . Str <$!> peekText idx
-- | Try extra hard to retrieve a Block value from the stack. Treats bar
-- Inline elements as if they were wrapped in 'Plain'.
peekBlockFuzzy :: LuaError e => Peeker e Block
peekBlockFuzzy = choice
[ peekBlock
, (\idx -> Plain <$!> peekInlinesFuzzy idx)
]
-- | Try extra-hard to return the value at the given index as a list of
-- blocks.
peekBlocksFuzzy :: LuaError e => Peeker e [Block]
peekBlocksFuzzy = choice
[ peekList peekBlockFuzzy
, (<$!>) pure . peekBlockFuzzy
]
pushListAttributes :: forall e. LuaError e => ListAttributes -> LuaE e ()
pushListAttributes (start, style, delimiter) =
@ -619,6 +805,26 @@ peekListAttributes = retrieving "ListAttributes" . peekTriple
peekRead
peekRead
-- * Orphan Instances
instance Pushable Inline where
push = pushInline
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
@ -633,6 +839,9 @@ instance Peekable Meta where
instance Peekable Pandoc where
peek = forcePeek . peekPandoc
instance Peekable Row where
peek = forcePeek . peekRow
instance Peekable Version where
peek = forcePeek . peekVersionFuzzy

View file

@ -16,13 +16,14 @@ module Text.Pandoc.Lua.Module.Pandoc
) where
import Prelude hiding (read)
import Control.Applicative (optional)
import Control.Monad ((>=>), forM_, when)
import Control.Applicative ((<|>), optional)
import Control.Monad ((>=>), (<$!>), forM_, when)
import Control.Monad.Catch (catch, throwM)
import Control.Monad.Except (throwError)
import Data.Default (Default (..))
import Data.Maybe (fromMaybe)
import HsLua as Lua hiding (pushModule)
import Data.Text (Text)
import HsLua as Lua hiding (Div, pushModule)
import HsLua.Class.Peekable (PeekError)
import System.Exit (ExitCode (..))
import Text.Pandoc.Class.PandocIO (runIO)
@ -65,10 +66,12 @@ pushModule = do
pushDocumentedFunction fn
rawset (nth 3)
forM_ otherConstructors addConstr
forM_ blockConstructors addConstr
forM_ inlineConstructors addConstr
let addConstructorTable constructors = do
-- add constructors to Inlines.constructor
newtable -- constructor
forM_ (inlineConstructors @PandocError) $ \fn -> do
forM_ constructors $ \fn -> do
let name = functionName fn
pushName name
pushName name
@ -76,9 +79,12 @@ pushModule = do
rawset (nth 3)
-- set as pandoc.Inline.constructor
pushName "Inline"
newtable *> pushName "constructor" *> pushvalue (nth 4) *> rawset (nth 3)
newtable *> pushName "constructor" *>
pushvalue (nth 4) *> rawset (nth 3)
rawset (nth 4)
pop 1 -- remaining constructor table
addConstructorTable (blockConstructors @PandocError)
addConstructorTable (inlineConstructors @PandocError)
return 1
inlineConstructors :: LuaError e => [DocumentedFunction e]
@ -86,7 +92,7 @@ inlineConstructors =
[ defun "Cite"
### liftPure2 Cite
<#> parameter (peekList peekCitation) "citations" "list of Citations" ""
<#> parameter peekFuzzyInlines "content" "Inline" "placeholder content"
<#> parameter peekInlinesFuzzy "content" "Inline" "placeholder content"
=#> functionResult pushInline "Inline" "cite element"
, defun "Code"
### liftPure2 (flip Code)
@ -99,7 +105,7 @@ inlineConstructors =
let attr = fromMaybe nullAttr mattr
title = fromMaybe mempty mtitle
in Image attr caption (src, title))
<#> parameter peekFuzzyInlines "Inlines" "caption" "image caption / alt"
<#> parameter peekInlinesFuzzy "Inlines" "caption" "image caption / alt"
<#> parameter peekText "string" "src" "path/URL of the image file"
<#> optionalParameter peekText "string" "title" "brief image description"
<#> optionalParameter peekAttr "Attr" "attr" "image attributes"
@ -112,7 +118,7 @@ inlineConstructors =
let attr = fromMaybe nullAttr mattr
title = fromMaybe mempty mtitle
in Link attr content (target, title))
<#> parameter peekFuzzyInlines "Inlines" "content" "text for this link"
<#> parameter peekInlinesFuzzy "Inlines" "content" "text for this link"
<#> parameter peekText "string" "target" "the link target"
<#> optionalParameter peekText "string" "title" "brief link description"
<#> optionalParameter peekAttr "Attr" "attr" "link attributes"
@ -124,12 +130,12 @@ inlineConstructors =
=#> functionResult pushInline "Inline" "math element"
, defun "Note"
### liftPure Note
<#> parameter peekFuzzyBlocks "content" "Blocks" "note content"
<#> parameter peekBlocksFuzzy "content" "Blocks" "note content"
=#> functionResult pushInline "Inline" "note"
, defun "Quoted"
### liftPure2 Quoted
<#> parameter peekQuoteType "quotetype" "QuoteType" "type of quotes"
<#> parameter peekFuzzyInlines "content" "Inlines" "inlines in quotes"
<#> parameter peekInlinesFuzzy "content" "Inlines" "inlines in quotes"
=#> functionResult pushInline "Inline" "quoted element"
, defun "RawInline"
### liftPure2 RawInline
@ -145,11 +151,11 @@ inlineConstructors =
=#> functionResult pushInline "Inline" "new space"
, defun "Span"
### liftPure2 (\inlns mattr -> Span (fromMaybe nullAttr mattr) inlns)
<#> parameter peekFuzzyInlines "content" "Inlines" "inline content"
<#> parameter peekInlinesFuzzy "content" "Inlines" "inline content"
<#> optionalParameter peekAttr "attr" "Attr" "additional attributes"
=#> functionResult pushInline "Inline" "span element"
, defun "Str"
### liftPure (\s -> s `seq` Str s)
### liftPure Str
<#> parameter peekText "text" "string" ""
=#> functionResult pushInline "Inline" "new Str object"
, mkInlinesConstr "Strong" Strong
@ -159,11 +165,119 @@ inlineConstructors =
, mkInlinesConstr "Underline" Underline
]
blockConstructors :: LuaError e => [DocumentedFunction e]
blockConstructors =
[ defun "BlockQuote"
### liftPure BlockQuote
<#> blocksParam
=#> blockResult "BlockQuote element"
, defun "BulletList"
### liftPure BulletList
<#> blockItemsParam "list items"
=#> blockResult "BulletList element"
, defun "CodeBlock"
### liftPure2 (\code mattr -> CodeBlock (fromMaybe nullAttr mattr) code)
<#> textParam "text" "code block content"
<#> optAttrParam
=#> blockResult "CodeBlock element"
, defun "DefinitionList"
### liftPure DefinitionList
<#> parameter (choice
[ peekList peekDefinitionItem
, \idx -> (:[]) <$!> peekDefinitionItem idx
])
"{{Inlines, {Blocks,...}},...}"
"content" "definition items"
=#> blockResult "DefinitionList element"
, defun "Div"
### liftPure2 (\content mattr -> Div (fromMaybe nullAttr mattr) content)
<#> blocksParam
<#> optAttrParam
=#> blockResult "Div element"
, defun "Header"
### liftPure3 (\lvl content mattr ->
Header lvl (fromMaybe nullAttr mattr) content)
<#> parameter peekIntegral "integer" "level" "heading level"
<#> parameter peekInlinesFuzzy "Inlines" "content" "inline content"
<#> optAttrParam
=#> blockResult "Header element"
, defun "HorizontalRule"
### return HorizontalRule
=#> blockResult "HorizontalRule element"
, defun "LineBlock"
### liftPure LineBlock
<#> parameter (peekList peekInlinesFuzzy) "{Inlines,...}" "content" "lines"
=#> blockResult "LineBlock element"
, defun "Null"
### return Null
=#> blockResult "Null element"
, defun "OrderedList"
### liftPure2 (\items mListAttrib ->
let defListAttrib = (1, DefaultStyle, DefaultDelim)
in OrderedList (fromMaybe defListAttrib mListAttrib) items)
<#> blockItemsParam "ordered list items"
<#> optionalParameter peekListAttributes "ListAttributes" "listAttributes"
"specifier for the list's numbering"
=#> blockResult "OrderedList element"
, defun "Para"
### liftPure Para
<#> parameter peekInlinesFuzzy "Inlines" "content" "paragraph content"
=#> blockResult "Para element"
, defun "Plain"
### liftPure Plain
<#> parameter peekInlinesFuzzy "Inlines" "content" "paragraph content"
=#> blockResult "Plain element"
, defun "RawBlock"
### liftPure2 RawBlock
<#> parameter peekFormat "Format" "format" "format of content"
<#> parameter peekText "string" "text" "raw content"
=#> blockResult "RawBlock element"
, defun "Table"
### (\capt colspecs thead tbodies tfoot mattr ->
let attr = fromMaybe nullAttr mattr
in return $! attr `seq` capt `seq` colspecs `seq` thead `seq` tbodies
`seq` tfoot `seq` Table attr capt colspecs thead tbodies tfoot)
<#> parameter peekCaption "Caption" "caption" "table caption"
<#> parameter (peekList peekColSpec) "{ColSpec,...}" "colspecs"
"column alignments and widths"
<#> parameter peekTableHead "TableHead" "head" "table head"
<#> parameter (peekList peekTableBody) "{TableBody,...}" "bodies"
"table bodies"
<#> parameter peekTableFoot "TableFoot" "foot" "table foot"
<#> optAttrParam
=#> blockResult "Table element"
]
where
blockResult = functionResult pushBlock "Block"
blocksParam = parameter peekBlocksFuzzy "Blocks" "content" "block content"
blockItemsParam = parameter peekItemsFuzzy "List of Blocks" "content"
peekItemsFuzzy idx = peekList peekBlocksFuzzy idx
<|> ((:[]) <$!> peekBlocksFuzzy idx)
textParam :: LuaError e => Text -> Text -> Parameter e Text
textParam = parameter peekText "string"
optAttrParam :: LuaError e => Parameter e (Maybe Attr)
optAttrParam = optionalParameter peekAttr "attr" "Attr" "additional attributes"
mkInlinesConstr :: LuaError e
=> Name -> ([Inline] -> Inline) -> DocumentedFunction e
mkInlinesConstr name constr = defun name
### liftPure (\x -> x `seq` constr x)
<#> parameter peekFuzzyInlines "content" "Inlines" ""
<#> parameter peekInlinesFuzzy "content" "Inlines" ""
=#> functionResult pushInline "Inline" "new object"
otherConstructors :: LuaError e => [DocumentedFunction e]
@ -181,8 +295,8 @@ otherConstructors =
})
<#> parameter peekText "string" "cid" "citation ID (e.g. bibtex key)"
<#> parameter peekRead "citation mode" "mode" "citation rendering mode"
<#> optionalParameter peekFuzzyInlines "prefix" "Inlines" ""
<#> optionalParameter peekFuzzyInlines "suffix" "Inlines" ""
<#> optionalParameter peekInlinesFuzzy "prefix" "Inlines" ""
<#> optionalParameter peekInlinesFuzzy "suffix" "Inlines" ""
<#> optionalParameter peekIntegral "note_num" "integer" "note number"
<#> optionalParameter peekIntegral "hash" "integer" "hash number"
=#> functionResult pushCitation "Citation" "new citation object"
@ -283,7 +397,7 @@ pushPipeError pipeErr = do
mkPandoc :: PandocLua NumResults
mkPandoc = liftPandocLua $ do
doc <- forcePeek $ do
blks <- peekBlocks (nthBottom 1)
blks <- peekBlocksFuzzy (nthBottom 1)
mMeta <- optional $ peekMeta (nthBottom 2)
pure $ Pandoc (fromMaybe nullMeta mMeta) blks
pushPandoc doc

View file

@ -35,13 +35,9 @@ pushModule = do
pushCloneTable :: LuaE PandocError NumResults
pushCloneTable = do
Lua.newtable
addFunction "Attr" $ cloneWith peekAttr pushAttr
addFunction "Block" $ cloneWith peekBlock pushBlock
addFunction "Inline" $ cloneWith peekInline pushInline
addFunction "Meta" $ cloneWith peekMeta Lua.push
addFunction "MetaValue" $ cloneWith peekMetaValue pushMetaValue
addFunction "ListAttributes" $ cloneWith peekListAttributes pushListAttributes
addFunction "Pandoc" $ cloneWith peekPandoc pushPandoc
return 1
cloneWith :: Peeker PandocError a

View file

@ -98,6 +98,126 @@ return {
assert.are_equal(count, 3)
end)
},
group "Block elements" {
group "BulletList" {
test('access items via property `content`', function ()
local para = pandoc.Para 'one'
local blist = pandoc.BulletList{{para}}
assert.are_same({{para}}, blist.content)
end),
test('property `content` uses fuzzy marshalling', function ()
local old = pandoc.Plain 'old'
local new = pandoc.Plain 'new'
local blist = pandoc.BulletList{{old}}
blist.content = {{new}}
assert.are_same({{new}}, blist:clone().content)
blist.content = new
assert.are_same({{new}}, blist:clone().content)
end),
},
group "OrderedList" {
test('access items via property `content`', function ()
local para = pandoc.Plain 'one'
local olist = pandoc.OrderedList{{para}}
assert.are_same({{para}}, olist.content)
end),
test('forgiving constructor', function ()
local plain = pandoc.Plain 'old'
local olist = pandoc.OrderedList({plain}, {3, 'Example', 'Period'})
local listAttribs = pandoc.ListAttributes(3, 'Example', 'Period')
assert.are_same(olist.listAttributes, listAttribs)
end),
test('has list attribute aliases', function ()
local olist = pandoc.OrderedList({}, {4, 'Decimal', 'OneParen'})
assert.are_equal(olist.start, 4)
assert.are_equal(olist.style, 'Decimal')
assert.are_equal(olist.delimiter, 'OneParen')
end)
},
group 'DefinitionList' {
test('access items via property `content`', function ()
local deflist = pandoc.DefinitionList{
{'apple', {{pandoc.Plain 'fruit'}, {pandoc.Plain 'company'}}},
{pandoc.Str 'coffee', 'Best when hot.'}
}
assert.are_equal(#deflist.content, 2)
assert.are_same(deflist.content[1][1], {pandoc.Str 'apple'})
assert.are_same(deflist.content[1][2][2],
{pandoc.Plain{pandoc.Str 'company'}})
assert.are_same(deflist.content[2][2],
{{pandoc.Plain{pandoc.Str 'Best when hot.'}}})
end),
test('modify items via property `content`', function ()
local deflist = pandoc.DefinitionList{
{'apple', {{{'fruit'}}, {{'company'}}}}
}
deflist.content[1][1] = pandoc.Str 'orange'
deflist.content[1][2][1] = {pandoc.Plain 'tasty fruit'}
local newlist = pandoc.DefinitionList{
{ {pandoc.Str 'orange'},
{{pandoc.Plain 'tasty fruit'}, {pandoc.Plain 'company'}}
}
}
assert.are_equal(deflist, newlist)
end),
},
group 'Para' {
test('access inline via property `content`', function ()
local para = pandoc.Para{'Moin, ', pandoc.Space(), 'Sylt!'}
assert.are_same(
para.content,
{pandoc.Str 'Moin, ', pandoc.Space(), pandoc.Str 'Sylt!'}
)
end),
test('modifying `content` changes the element', function ()
local para = pandoc.Para{'Moin, ', pandoc.Space(), pandoc.Str 'Sylt!'}
para.content[3] = 'Hamburg!'
assert.are_same(
para:clone().content,
{pandoc.Str 'Moin, ', pandoc.Space(), pandoc.Str 'Hamburg!'}
)
para.content = 'Huh'
assert.are_same(
para:clone().content,
{pandoc.Str 'Huh'}
)
end),
},
group 'LineBlock' {
test('access lines via property `content`', function ()
local spc = pandoc.Space()
local lineblock = pandoc.LineBlock{
{'200', spc, 'Main', spc, 'St.'},
{'Berkeley', spc, 'CA', spc, '94718'}
}
assert.are_equal(#lineblock.content, 2) -- has two lines
assert.are_same(lineblock.content[2][1], pandoc.Str 'Berkeley')
end),
test('modifying `content` alter the element', function ()
local spc = pandoc.Space()
local lineblock = pandoc.LineBlock{
{'200', spc, 'Main', spc, 'St.'},
{'Berkeley', spc, 'CA', spc, '94718'}
}
lineblock.content[1][1] = '404'
assert.are_same(
lineblock:clone().content[1],
{pandoc.Str '404', spc, pandoc.Str 'Main', spc, pandoc.Str 'St.'}
)
lineblock.content = {{'line1'}, {'line2'}}
assert.are_same(
lineblock:clone(),
pandoc.LineBlock{
{pandoc.Str 'line1'},
{pandoc.Str 'line2'}
}
)
end)
}
},
group 'HTML-like attribute tables' {
test('in element constructor', function ()
local html_attributes = {