packages feed

pandoc-lua-marshal 0.1.2 → 0.1.3

raw patch · 25 files changed

+941/−172 lines, 25 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Text.Pandoc.Lua.Marshal.Filter: newtype Filter
+ Text.Pandoc.Lua.Marshal.Filter: WalkForEachType :: WalkingOrder
+ Text.Pandoc.Lua.Marshal.Filter: WalkTopdown :: WalkingOrder
+ Text.Pandoc.Lua.Marshal.Filter: [filterMap] :: Filter -> Map Name FilterFunction
+ Text.Pandoc.Lua.Marshal.Filter: [filterWalkingOrder] :: Filter -> WalkingOrder
+ Text.Pandoc.Lua.Marshal.Filter: data Filter
+ Text.Pandoc.Lua.Marshal.Filter: data WalkingOrder
+ Text.Pandoc.Lua.Marshal.Row: mkRow :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.Row: peekRow :: LuaError e => Peeker e Row
+ Text.Pandoc.Lua.Marshal.Row: peekRowFuzzy :: LuaError e => Peeker e Row
+ Text.Pandoc.Lua.Marshal.Row: pushRow :: LuaError e => Row -> LuaE e ()
+ Text.Pandoc.Lua.Marshal.Row: typeRow :: LuaError e => DocumentedType e Row
+ Text.Pandoc.Lua.Marshal.TableFoot: mkTableFoot :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.TableFoot: peekTableFoot :: LuaError e => Peeker e TableFoot
+ Text.Pandoc.Lua.Marshal.TableFoot: pushTableFoot :: LuaError e => TableFoot -> LuaE e ()
+ Text.Pandoc.Lua.Marshal.TableFoot: typeTableFoot :: LuaError e => DocumentedType e TableFoot
+ Text.Pandoc.Lua.Marshal.TableHead: mkTableHead :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.TableHead: peekTableHead :: LuaError e => Peeker e TableHead
+ Text.Pandoc.Lua.Marshal.TableHead: pushTableHead :: LuaError e => TableHead -> LuaE e ()
+ Text.Pandoc.Lua.Marshal.TableHead: typeTableHead :: LuaError e => DocumentedType e TableHead
+ Text.Pandoc.Lua.Marshal.TableParts: mkRow :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.TableParts: mkTableFoot :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.TableParts: mkTableHead :: LuaError e => DocumentedFunction e
+ Text.Pandoc.Lua.Marshal.TableParts: peekRowFuzzy :: LuaError e => Peeker e Row
- Text.Pandoc.Lua.Marshal.Filter: Filter :: Map Name FilterFunction -> Filter
+ Text.Pandoc.Lua.Marshal.Filter: Filter :: WalkingOrder -> Map Name FilterFunction -> Filter
- Text.Pandoc.Lua.Marshal.TableParts: pushRow :: LuaError e => Pusher e Row
+ Text.Pandoc.Lua.Marshal.TableParts: pushRow :: LuaError e => Row -> LuaE e ()
- Text.Pandoc.Lua.Marshal.TableParts: pushTableFoot :: LuaError e => Pusher e TableFoot
+ Text.Pandoc.Lua.Marshal.TableParts: pushTableFoot :: LuaError e => TableFoot -> LuaE e ()
- Text.Pandoc.Lua.Marshal.TableParts: pushTableHead :: LuaError e => Pusher e TableHead
+ Text.Pandoc.Lua.Marshal.TableParts: pushTableHead :: LuaError e => TableHead -> LuaE e ()

Files

CHANGELOG.md view
@@ -2,6 +2,43 @@  `pandoc-lua-marshal` uses [PVP Versioning][]. +## 0.1.3++Release pending.++### Lua changes++-   The traversal order of filters can now be selected by setting+    the key `traverse` to either `'topdown'` or `'typewise'`; the+    default remains `'typewise'`.++    Topdown traversals can be cut short by returning `false` as a+    second value from the filter function. No child-element of+    the returned element is processed in that case.++-   All types can be compared. Previously, comparing values of+    different types would lead to errors in a number of cases.++-   Lists now have an `__eq` metamethod. List equality is checked+    by comparing both lists element-wise. Two lists are equal if+    they have the same type and have equal elements.++-   If start indices in `List:find` and `List:find_if` are+    negative the start index is relative to the list length.++-   TableFoot, TableHead, and Row values are marshaled as+    userdata objects.++### Haskell code++-   Text.Pandoc.Lua.Marshal.Filter exports the new type+    `WalkingOrder`. The type `Filter` now contains the the+    traversal specifier as a field.++-   New modules for TableFoot, TableHead, and Row, defining the+    usual marshaling methods and constructor functions for these+    types.+ ## 0.1.2  Released 2021-12-10.
cbits/listmod.c view
@@ -5,6 +5,13 @@  #define LIST_T "List" +/* compatibility with older Lua versions, which did not define this in the+ * header. */+#ifndef LUA_LOADED_TABLE+/* key, in the registry, for table of loaded modules */+#define LUA_LOADED_TABLE	"_LOADED"+#endif+ /* ** Placeholder function. */@@ -14,7 +21,7 @@   ); } -/* translate a relative table position: negative means back from end */+/* Translate a relative table position: negative means back from end */ static lua_Integer posrelat (lua_Integer pos, size_t len) {   if (pos >= 0) return pos;   else if (0u - (size_t)pos > len) return 0;@@ -83,6 +90,42 @@ }  /*+** Checks equality. Two lists are equal if and only if they have the same+** metatable and if all items are equal.+*/+static int list_eq (lua_State *L) {+  lua_settop(L, 2);+  /* compare meta tables */+  if (!(lua_getmetatable(L, 1) &&+        lua_getmetatable(L, 2) &&+        lua_rawequal(L, -1, -2))) {+    lua_pushboolean(L, 0);+    return 1;+  };+  lua_pop(L, 2);  /* remove metatables */++  /* ensure both lists have the same length */+  lua_Integer len1 = luaL_len(L, 1);+  lua_Integer len2 = luaL_len(L, 2);+  if (len1 != len2) {+    lua_pushboolean(L, 0);+    return 1;+  }++  /* check element-wise equality  */+  for (lua_Integer i = 1; i <= len1; i++) {+    lua_geti(L, 1, i);+    lua_geti(L, 2, i);+    if (!lua_compare(L, -1, -2, LUA_OPEQ)) {+      lua_pushboolean(L, 0);+      return 1;+    }+  }+  lua_pushboolean(L, 1);+  return 1;+}++/* ** Appends the second list to the first. */ static int list_extend (lua_State *L) {@@ -131,8 +174,8 @@ static int list_find (lua_State *L) {   lua_settop(L, 3);   luaL_checktype(L, 1, LUA_TTABLE);-  lua_Integer start = luaL_optinteger(L, 3, 1);   lua_Integer len = luaL_len(L, 1);+  lua_Integer start = posrelat(luaL_optinteger(L, 3, 1), len);   for (lua_Integer i = start; i <= len; i++) {     lua_geti(L, 1, i);     if (lua_compare(L, 2, -1, LUA_OPEQ)) {@@ -153,8 +196,8 @@   lua_settop(L, 3);   luaL_checktype(L, 1, LUA_TTABLE);   luaL_checktype(L, 2, LUA_TFUNCTION);-  lua_Integer start = luaL_optinteger(L, 3, 1);   lua_Integer len = luaL_len(L, 1);+  lua_Integer start = posrelat(luaL_optinteger(L, 3, 1), len);   for (lua_Integer i = start; i <= len; i++) {     lua_pushvalue(L, 2);  /* predicate function */     lua_geti(L, 1, i);@@ -194,7 +237,7 @@   luaL_checktype(L, 2, LUA_TFUNCTION);   lua_Integer len = luaL_len(L, 1);   lua_createtable(L, len, 0);  /* create new table */-  lua_getmetatable(L, 1);+  luaL_getmetatable(L, LIST_T);  /* make result a generic list */   lua_setmetatable(L, 3);   for (lua_Integer i = 1; i <= len; i++) {     lua_pushvalue(L, 2);  /* map function */@@ -250,6 +293,7 @@  static const luaL_Reg list_funcs[] = {   {"__concat", list_concat},+  {"__eq", list_eq},   {"clone", list_clone},   {"extend", list_extend},   {"filter", list_filter},
pandoc-lua-marshal.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                pandoc-lua-marshal-version:             0.1.2+version:             0.1.3 synopsis:            Use pandoc types in Lua description:         This package provides functions to marshal and unmarshal                      pandoc document types to and from Lua.@@ -91,9 +91,13 @@                      , Text.Pandoc.Lua.Marshal.MetaValue                      , Text.Pandoc.Lua.Marshal.Pandoc                      , Text.Pandoc.Lua.Marshal.QuoteType+                     , Text.Pandoc.Lua.Marshal.Row                      , Text.Pandoc.Lua.Marshal.SimpleTable+                     , Text.Pandoc.Lua.Marshal.TableFoot+                     , Text.Pandoc.Lua.Marshal.TableHead                      , Text.Pandoc.Lua.Marshal.TableParts   other-modules:       Text.Pandoc.Lua.Marshal.Shared+                     , Text.Pandoc.Lua.Topdown                      , Text.Pandoc.Lua.SpliceList                      , Text.Pandoc.Lua.Walk 
src/Text/Pandoc/Lua/Marshal/Attr.hs view
@@ -38,9 +38,9 @@ typeAttr :: LuaError e => DocumentedType e Attr typeAttr = deftype "Attr"   [ operation Eq $ lambda-    ### liftPure2 (==)-    <#> parameter peekAttr "a1" "Attr" ""-    <#> parameter peekAttr "a2" "Attr" ""+    ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+    <#> parameter (optional . peekAttr) "a" "Attr" ""+    <#> parameter (optional . peekAttr) "b" "Attr" ""     =#> functionResult pushBool "boolean" "whether the two are equal"   , operation Tostring $ lambda     ### liftPure show
src/Text/Pandoc/Lua/Marshal/Block.hs view
@@ -25,7 +25,7 @@   , walkBlocksStraight   ) where -import Control.Applicative ((<|>))+import Control.Applicative ((<|>), optional) import Control.Monad.Catch (throwM) import Control.Monad ((<$!>)) import Data.Data (showConstr, toConstr)@@ -110,9 +110,9 @@ typeBlock :: forall e. LuaError e => DocumentedType e Block typeBlock = deftype "Block"   [ operation Eq $ lambda-    ### liftPure2 (==)-    <#> parameter peekBlockFuzzy "Block" "a" ""-    <#> parameter peekBlockFuzzy "Block" "b" ""+    ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+    <#> parameter (optional . peekBlockFuzzy) "Block" "a" ""+    <#> parameter (optional . peekBlockFuzzy) "Block" "b" ""     =#> boolResult "whether the two values are equal"   , operation Tostring $ lambda     ### liftPure show
src/Text/Pandoc/Lua/Marshal/Citation.hs view
@@ -38,7 +38,7 @@              => DocumentedType e Citation typeCitation = deftype "Citation"   [ operation Eq $ lambda-    ### liftPure2 (==)+    ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))     <#> parameter (optional . peekCitation) "Citation" "a" ""     <#> parameter (optional . peekCitation) "Citation" "b" ""     =#> functionResult pushBool "boolean" "true iff the citations are equal"
src/Text/Pandoc/Lua/Marshal/Filter.hs view
@@ -13,6 +13,7 @@ module Text.Pandoc.Lua.Marshal.Filter   ( -- * Filters     Filter (..)+  , WalkingOrder (..)   , peekFilter   , lookup   , member@@ -28,7 +29,7 @@   ) where  import Prelude hiding (lookup)-import Control.Applicative ((<|>))+import Control.Applicative ((<|>), optional) import Control.Monad ((<$!>)) import Data.Data   ( Data, dataTypeConstrs, dataTypeName, dataTypeOf@@ -63,8 +64,18 @@  -- | Collection of filter functions (at most one function per element -- constructor)-newtype Filter = Filter (Map Name FilterFunction)+data Filter = Filter+  { filterWalkingOrder :: WalkingOrder+  , filterMap :: Map Name FilterFunction+  } +-- | Description of how an AST should be traversed.+data WalkingOrder+  = WalkForEachType  -- ^ Process each type separately, traversing the+                     -- tree bottom-up (leaves to root) for each type.+  | WalkTopdown      -- ^ Traverse the tree top-down, from root to+                     -- leaves and depth first, in a single traversal.+ -- | Retrieves a default `Filter` object from the stack, suitable for -- filtering a full document. peekFilter :: LuaError e => Peeker e Filter@@ -87,15 +98,21 @@         runPeek (peekFilterFunction top `lastly` pop 1) >>= \case           Success fn -> pure $ Map.insert constr fn acc           Failure {} -> pure acc-  Filter <$!> foldrM go Map.empty fnNames+  walkingSequence <- do+    _ <- liftLua $ getfield idx "traverse"+    optional (peekText top) `lastly` pop 1 >>= \case+      Just "typewise" -> pure WalkForEachType+      Just "topdown"  -> pure WalkTopdown+      _               -> pure WalkForEachType+  Filter walkingSequence <$!> foldrM go Map.empty fnNames  -- | Looks up a filter function in a Lua 'Filter'. lookup :: Name -> Filter -> Maybe FilterFunction-lookup name (Filter filterMap) = name `Map.lookup` filterMap+lookup name = (name `Map.lookup`) . filterMap  -- | Checks whether the 'Filter' contains a function of the given name. member :: Name -> Filter -> Bool-member name (Filter filterMap) = name `Map.member` filterMap+member name = (name `Map.member`) . filterMap  -- | Filter function names for a given type. valueFunctionNames :: forall a. Data a => Proxy a -> [Name]@@ -115,6 +132,8 @@   fromString . (++ "s") . tyconUQname . dataTypeName . dataTypeOf   $ (undefined :: a) +-- | Finds the best filter function for a given element; returns+-- 'Nothing' if no such function exists. getFunctionFor :: forall a. Data a => Filter -> a -> Maybe FilterFunction getFunctionFor filter' x =   let constrName = fromString . showConstr . toConstr $ x
src/Text/Pandoc/Lua/Marshal/Inline.hs view
@@ -25,6 +25,7 @@   , walkInlinesStraight   ) where +import Control.Applicative (optional) import Control.Monad.Catch (throwM) import Control.Monad ((<$!>)) import Data.Data (showConstr, toConstr)@@ -106,9 +107,9 @@     <#> parameter peekInline "inline" "Inline" "Object"     =#> functionResult pushString "string" "stringified Inline"   , operation Eq $ defun "__eq"-      ### liftPure2 (==)-      <#> parameter peekInline "a" "Inline" ""-      <#> parameter peekInline "b" "Inline" ""+      ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+      <#> parameter (optional . peekInline) "a" "Inline" ""+      <#> parameter (optional . peekInline) "b" "Inline" ""       =#> functionResult pushBool "boolean" "whether the two are equal"   ]   [ possibleProperty "attr" "element attributes"
src/Text/Pandoc/Lua/Marshal/ListAttributes.hs view
@@ -19,6 +19,7 @@   , pushListNumberStyle   ) where +import Control.Applicative (optional) import Data.Maybe (fromMaybe) import HsLua import Text.Pandoc.Definition@@ -28,9 +29,9 @@ typeListAttributes :: LuaError e => DocumentedType e ListAttributes typeListAttributes = deftype "ListAttributes"   [ operation Eq $ lambda-    ### liftPure2 (==)-    <#> parameter peekListAttributes "a" "ListAttributes" ""-    <#> parameter peekListAttributes "b" "ListAttributes" ""+    ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+    <#> parameter (optional . peekListAttributes) "a" "ListAttributes" ""+    <#> parameter (optional . peekListAttributes) "b" "ListAttributes" ""     =#> functionResult pushBool "boolean" "whether the two are equal"   ]   [ property "start" "number of the first list item"
src/Text/Pandoc/Lua/Marshal/MetaValue.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE LambdaCase           #-} {-# LANGUAGE OverloadedStrings    #-} {-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE TypeApplications     #-} {- | Copyright               : © 2021 Albert Krewinkel SPDX-License-Identifier : MIT@@ -23,6 +24,7 @@   ( peekInline, peekInlines, peekInlinesFuzzy, pushInlines ) import Text.Pandoc.Lua.Marshal.List (pushPandocList) import Text.Pandoc.Definition (MetaValue (..))+import qualified Data.Text as T  -- | Push a 'MetaValue' element to the top of the Lua stack. pushMetaValue :: LuaError e => Pusher e MetaValue@@ -43,6 +45,11 @@     TypeBoolean -> MetaBool <$!> peekBool idx      TypeString  -> MetaString <$!> peekText idx++    TypeNumber  -> MetaString . T.pack <$>+      (liftLua (isinteger idx) >>= \case+          False -> show <$!> peekRealFloat @Double idx+          True  -> show <$!> peekIntegral @Prelude.Integer idx)      TypeUserdata -> -- Allow singleton Inline or Block elements       (MetaInlines . (:[]) <$!> peekInline idx) <|>
src/Text/Pandoc/Lua/Marshal/Pandoc.hs view
@@ -26,7 +26,7 @@ import Text.Pandoc.Lua.Marshal.Block   (peekBlocksFuzzy, pushBlocks, walkBlockSplicing, walkBlocksStraight) import Text.Pandoc.Lua.Marshal.Inline (walkInlineSplicing, walkInlinesStraight)-import Text.Pandoc.Lua.Marshal.Filter (Filter, peekFilter)+import Text.Pandoc.Lua.Marshal.Filter import Text.Pandoc.Lua.Marshal.MetaValue (peekMetaValue, pushMetaValue) import Text.Pandoc.Lua.Marshal.Shared (walkBlocksAndInlines) import Text.Pandoc.Lua.Walk (applyStraight)@@ -44,7 +44,7 @@ typePandoc :: LuaError e => DocumentedType e Pandoc typePandoc = deftype "Pandoc"   [ operation Eq $ defun "__eq"-     ### liftPure2 (==)+     ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))      <#> parameter (optional . peekPandoc) "doc1" "pandoc" ""      <#> parameter (optional . peekPandoc) "doc2" "pandoc" ""      =#> functionResult pushBool "boolean" "true iff the two values are equal"@@ -61,10 +61,13 @@       (peekMeta, \(Pandoc _ blks) meta -> Pandoc meta blks)    , method $ defun "walk"-    ### (\doc filter' ->-               walkBlocksAndInlines filter' doc-           >>= applyMetaFunction filter'-           >>= applyPandocFunction filter')+    ### (\doc filter' -> case filterWalkingOrder filter' of+            WalkForEachType -> walkBlocksAndInlines filter' doc+                           >>= applyMetaFunction filter'+                           >>= applyPandocFunction filter'+            WalkTopdown     -> applyPandocFunction filter' doc+                           >>= applyMetaFunction filter'+                           >>= walkBlocksAndInlines filter')     <#> parameter peekPandoc "Pandoc" "self" ""     <#> parameter peekFilter "Filter" "lua_filter" "table of filter functions"     =#> functionResult pushPandoc "Pandoc" "modified element"
+ src/Text/Pandoc/Lua/Marshal/Row.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE LambdaCase           #-}+{- |+Copyright               : © 2021 Albert Krewinkel+SPDX-License-Identifier : MIT+Maintainer              : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshaling/unmarshaling functions of 'Row' values.+-}+module Text.Pandoc.Lua.Marshal.Row+  ( peekRow+  , peekRowFuzzy+  , pushRow+  , typeRow+  , mkRow+  ) where++import Control.Applicative (optional)+import Control.Monad ((<$!>))+import Data.Maybe (fromMaybe)+import HsLua+import Text.Pandoc.Lua.Marshal.Attr (peekAttr, pushAttr)+import Text.Pandoc.Lua.Marshal.Cell (peekCellFuzzy, pushCell)+import Text.Pandoc.Lua.Marshal.Filter (peekFilter)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Marshal.Shared (walkBlocksAndInlines)+import Text.Pandoc.Definition++-- | Push a table Row as a table with fields @attr@, @alignment@,+-- @row_span@, @col_span@, and @contents@.+pushRow :: LuaError e => Row -> LuaE e ()+pushRow = pushUD typeRow++-- | Retrieves a 'Cell' object from the stack.+peekRow :: LuaError e => Peeker e Row+peekRow = peekUD typeRow++-- | Retrieves a 'Cell' from the stack, accepting either a 'pandoc Cell'+-- userdata object or a table with fields @attr@, @alignment@, @row_span@,+-- @col_span@, and @contents@.+peekRowFuzzy :: LuaError e => Peeker e Row+peekRowFuzzy idx = liftLua (ltype idx) >>= \case+  TypeUserdata -> peekRow idx+  TypeTable -> uncurry Row <$!> peekPair peekAttr (peekList peekCellFuzzy) idx+  _ -> failPeek =<< typeMismatchMessage "Cell or table" idx++-- | Row object type.+typeRow :: LuaError e => DocumentedType e Row+typeRow = deftype "pandoc Row"+  [ operation Eq $ defun "__eq"+     ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+     <#> parameter (optional . peekRow) "Row" "self" ""+     <#> parameter (optional . peekRow) "any" "object" ""+     =#> functionResult pushBool "boolean" "true iff the two values are equal"+  , operation Tostring $ lambda+    ### liftPure show+    <#> parameter peekRow "Row" "self" ""+    =#> functionResult pushString "string" "native Haskell representation"+  ]+  [ property "attr" "row attributes"+      (pushAttr, \(Row attr _) -> attr)+      (peekAttr, \(Row _ cells) attr ->+                   Row attr cells)+  , property "cells" "row cells"+      (pushPandocList pushCell, \(Row _ cells) -> cells)+      (peekList peekCellFuzzy, \(Row attr _) cells ->+                                 Row attr cells)++  , alias "identifier" "cell ID"         ["attr", "identifier"]+  , alias "classes"    "cell classes"    ["attr", "classes"]+  , alias "attributes" "cell attributes" ["attr", "attributes"]++  , method $ defun "clone"+    ### return+    <#> parameter peekRow "Row" "self" ""+    =#> functionResult pushRow "Row" "cloned object"++  , method $ defun "walk"+    ### flip walkBlocksAndInlines+    <#> parameter peekRow "Row" "self" ""+    <#> parameter peekFilter "Filter" "lua_filter" "table of filter functions"+    =#> functionResult pushRow "Row" "modified cell"+  ]++-- | Constructor function for 'Row' values.+mkRow :: LuaError e => DocumentedFunction e+mkRow = defun "Row"+  ### liftPure2 (\mCells mAttr -> Row+                  (fromMaybe nullAttr mAttr)+                  (fromMaybe [] mCells))+  <#> optionalParameter (peekList peekCellFuzzy) "{Cell,...}" "cells"+        "row cells"+  <#> optionalParameter peekAttr "Attr" "attr" "cell attributes"+  =#> functionResult pushRow "Row" "new Row object"
src/Text/Pandoc/Lua/Marshal/Shared.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE OverloadedStrings    #-} {- | Copyright   : © 2021 Albert Krewinkel License     : MIT@@ -11,26 +12,73 @@     walkBlocksAndInlines   ) where +import Prelude hiding (lookup) import Control.Monad ((>=>))-import HsLua (LuaE, LuaError)+import HsLua import {-# SOURCE #-} Text.Pandoc.Lua.Marshal.Block-  ( walkBlockSplicing, walkBlocksStraight )-import Text.Pandoc.Lua.Marshal.Filter (Filter) import {-# SOURCE #-} Text.Pandoc.Lua.Marshal.Inline-  ( walkInlineSplicing, walkInlinesStraight )+import Text.Pandoc.Lua.Marshal.Filter import Text.Pandoc.Definition-import Text.Pandoc.Lua.Walk (SpliceList, Walkable)+import Text.Pandoc.Lua.Topdown+import Text.Pandoc.Lua.Walk+import Text.Pandoc.Walk  -- | Walk blocks and inlines. walkBlocksAndInlines :: (LuaError e,                          Walkable (SpliceList Block) a,                          Walkable (SpliceList Inline) a,                          Walkable [Block] a,-                         Walkable [Inline] a)+                         Walkable [Inline] a,+                         Walkable Topdown a)                      => Filter                      -> a -> LuaE e a-walkBlocksAndInlines f =-      walkInlineSplicing f-  >=> walkInlinesStraight f-  >=> walkBlockSplicing f-  >=> walkBlocksStraight f+walkBlocksAndInlines filter' =+  case filterWalkingOrder filter' of+    WalkTopdown     -> walkM (applyFilterTopdown filter')+    WalkForEachType -> walkInlineSplicing filter'+                   >=> walkInlinesStraight filter'+                   >=> walkBlockSplicing filter'+                   >=> walkBlocksStraight filter'++-- | Applies a filter by processing the root node(s) first and descending+-- towards the leaves depth-first.+applyFilterTopdown :: LuaError e+                   => Filter+                   -> Topdown -> LuaE e Topdown+applyFilterTopdown filter' topdown@(Topdown _ node) =+  case node of+    TBlock x ->+      case filter' `getFunctionFor` x of+        Nothing ->+          pure topdown+        Just fn -> do+          (blocks, ctrl) <-+            applySplicingFunction fn pushBlock peekBlocksFuzzy x+          pure $ Topdown ctrl $ TBlocks blocks++    TBlocks xs ->+      case "Blocks" `lookup` filter' of+        Nothing ->+          pure topdown+        Just fn -> do+          (blocks, ctrl) <-+            applyStraightFunction fn pushBlocks peekBlocksFuzzy xs+          pure $ Topdown ctrl $ TBlocks blocks++    TInline x ->+      case filter' `getFunctionFor` x of+        Nothing ->+          pure topdown+        Just fn -> do+          (inlines, ctrl) <-+            applySplicingFunction fn pushInline peekInlinesFuzzy x+          pure $ Topdown ctrl $ TInlines inlines++    TInlines xs ->+      case "Inlines" `lookup` filter' of+        Nothing ->+          pure topdown+        Just fn -> do+          (inlines, ctrl) <-+            applyStraightFunction fn pushInlines peekInlinesFuzzy xs+          pure $ Topdown ctrl $ TInlines inlines
src/Text/Pandoc/Lua/Marshal/SimpleTable.hs view
@@ -17,6 +17,8 @@   )   where +import Control.Applicative (optional)+import Data.Maybe (fromMaybe) import HsLua as Lua import Text.Pandoc.Lua.Marshal.Alignment (peekAlignment, pushAlignment) import Text.Pandoc.Lua.Marshal.Block (peekBlocksFuzzy, pushBlocks)@@ -36,9 +38,9 @@ typeSimpleTable :: LuaError e => DocumentedType e SimpleTable typeSimpleTable = deftype "SimpleTable"   [ operation Eq $ lambda-    ### liftPure2 (==)-    <#> udparam typeSimpleTable "a" ""-    <#> udparam typeSimpleTable "b" ""+    ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+    <#> parameter (optional . peekSimpleTable) "value" "a" ""+    <#> parameter (optional . peekSimpleTable) "value" "b" ""     =#> functionResult pushBool "boolean" "whether the two objects are equal"   , operation Tostring $ lambda     ### liftPure show
+ src/Text/Pandoc/Lua/Marshal/TableFoot.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings    #-}+{- |+Copyright               : © 2021 Albert Krewinkel+SPDX-License-Identifier : MIT+Maintainer              : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshaling/unmarshaling functions of 'TableFoot' values.+-}+module Text.Pandoc.Lua.Marshal.TableFoot+  ( peekTableFoot+  , pushTableFoot+  , typeTableFoot+  , mkTableFoot+  ) where++import Control.Applicative (optional)+import Data.Maybe (fromMaybe)+import HsLua+import Text.Pandoc.Lua.Marshal.Attr (peekAttr, pushAttr)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Marshal.Row (peekRowFuzzy, pushRow)+import Text.Pandoc.Definition++-- | Push a TableFoot as a userdata value.+pushTableFoot :: LuaError e => TableFoot -> LuaE e ()+pushTableFoot = pushUD typeTableFoot++-- | Retrieves a 'Cell' from the stack.+peekTableFoot :: LuaError e => Peeker e TableFoot+peekTableFoot = peekUD typeTableFoot++-- | Row object type.+typeTableFoot :: LuaError e => DocumentedType e TableFoot+typeTableFoot = deftype "pandoc TableFoot"+  [ operation Eq $ defun "__eq"+     ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+     <#> parameter (optional . peekTableFoot) "TableFoot" "self" ""+     <#> parameter (optional . peekTableFoot) "any" "object" ""+     =#> functionResult pushBool "boolean" "true iff the two values are equal"+  , operation Tostring $ lambda+    ### liftPure show+    <#> parameter peekTableFoot "TableFoot" "self" ""+    =#> functionResult pushString "string" "native Haskell representation"+  ]+  [ property "attr" "table foot attributes"+      (pushAttr, \(TableFoot attr _) -> attr)+      (peekAttr, \(TableFoot _ cells) attr ->+                   TableFoot attr cells)+  , property "rows" "footer rows"+      (pushPandocList pushRow, \(TableFoot _ rows) -> rows)+      (peekList peekRowFuzzy, \(TableFoot attr _) rows ->+                                TableFoot attr rows)++  , alias "identifier" "cell ID"         ["attr", "identifier"]+  , alias "classes"    "cell classes"    ["attr", "classes"]+  , alias "attributes" "cell attributes" ["attr", "attributes"]++  , method $ defun "clone"+    ### return+    <#> parameter peekTableFoot "TableFoot" "self" ""+    =#> functionResult pushTableFoot "TableFoot" "cloned object"+  ]++-- | Constructor function for 'Row' values.+mkTableFoot :: LuaError e => DocumentedFunction e+mkTableFoot = defun "TableFoot"+  ### liftPure2 (\mCells mAttr -> TableFoot+                  (fromMaybe nullAttr mAttr)+                  (fromMaybe [] mCells))+  <#> optionalParameter (peekList peekRowFuzzy) "{Row,...}" "rows"+        "footer rows"+  <#> optionalParameter peekAttr "Attr" "attr" "table foot attributes"+  =#> functionResult pushTableFoot "TableFoot" "new TableFoot object"
+ src/Text/Pandoc/Lua/Marshal/TableHead.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings    #-}+{- |+Copyright               : © 2021 Albert Krewinkel+SPDX-License-Identifier : MIT+Maintainer              : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshaling/unmarshaling functions of 'TableHead' values.+-}+module Text.Pandoc.Lua.Marshal.TableHead+  ( peekTableHead+  , pushTableHead+  , typeTableHead+  , mkTableHead+  ) where++import Control.Applicative (optional)+import Data.Maybe (fromMaybe)+import HsLua+import Text.Pandoc.Lua.Marshal.Attr (peekAttr, pushAttr)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Marshal.Row (peekRowFuzzy, pushRow)+import Text.Pandoc.Definition++-- | Push a TableHead as a userdata value.+pushTableHead :: LuaError e => TableHead -> LuaE e ()+pushTableHead = pushUD typeTableHead++-- | Retrieves a 'Cell' from the stack.+peekTableHead :: LuaError e => Peeker e TableHead+peekTableHead = peekUD typeTableHead++-- | Row object type.+typeTableHead :: LuaError e => DocumentedType e TableHead+typeTableHead = deftype "pandoc TableHead"+  [ operation Eq $ defun "__eq"+     ### liftPure2 (\a b -> fromMaybe False ((==) <$> a <*> b))+     <#> parameter (optional . peekTableHead) "TableHead" "self" ""+     <#> parameter (optional . peekTableHead) "any" "object" ""+     =#> functionResult pushBool "boolean" "true iff the two values are equal"+  , operation Tostring $ lambda+    ### liftPure show+    <#> parameter peekTableHead "TableHead" "self" ""+    =#> functionResult pushString "string" "native Haskell representation"+  ]+  [ property "attr" "table head attributes"+      (pushAttr, \(TableHead attr _) -> attr)+      (peekAttr, \(TableHead _ cells) attr ->+                   TableHead attr cells)+  , property "rows" "header rows"+      (pushPandocList pushRow, \(TableHead _ rows) -> rows)+      (peekList peekRowFuzzy, \(TableHead attr _) rows ->+                                TableHead attr rows)++  , alias "identifier" "cell ID"         ["attr", "identifier"]+  , alias "classes"    "cell classes"    ["attr", "classes"]+  , alias "attributes" "cell attributes" ["attr", "attributes"]++  , method $ defun "clone"+    ### return+    <#> parameter peekTableHead "TableHead" "self" ""+    =#> functionResult pushTableHead "TableHead" "cloned object"+  ]++-- | Constructor function for 'Row' values.+mkTableHead :: LuaError e => DocumentedFunction e+mkTableHead = defun "TableHead"+  ### liftPure2 (\mRows mAttr -> TableHead+                  (fromMaybe nullAttr mAttr)+                  (fromMaybe [] mRows))+  <#> optionalParameter (peekList peekRowFuzzy) "{Row,...}" "rows"+        "header rows"+  <#> optionalParameter peekAttr "Attr" "attr" "table head attributes"+  =#> functionResult pushTableHead "TableHead" "new TableHead object"
src/Text/Pandoc/Lua/Marshal/TableParts.hs view
@@ -14,6 +14,7 @@   , peekColSpec   , pushColSpec   , peekRow+  , peekRowFuzzy   , pushRow   , peekTableBody   , pushTableBody@@ -21,6 +22,10 @@   , pushTableFoot   , peekTableHead   , pushTableHead+    -- * Constructors+  , mkRow+  , mkTableFoot+  , mkTableHead   ) where  import Control.Applicative (optional)@@ -30,10 +35,12 @@ import Text.Pandoc.Lua.Marshal.Attr (peekAttr, pushAttr) import {-# SOURCE #-} Text.Pandoc.Lua.Marshal.Block   ( peekBlocksFuzzy, pushBlocks )-import Text.Pandoc.Lua.Marshal.Cell (peekCellFuzzy, pushCell) import {-# SOURCE #-} Text.Pandoc.Lua.Marshal.Inline   ( peekInlinesFuzzy, pushInlines ) import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Marshal.Row+import Text.Pandoc.Lua.Marshal.TableFoot+import Text.Pandoc.Lua.Marshal.TableHead import Text.Pandoc.Definition  -- | Push Caption element@@ -69,17 +76,6 @@   (ColWidth w)    -> push w   ColWidthDefault -> pushnil --- | 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 peekCellFuzzy)- -- | Pushes a 'TableBody' value as a Lua table with fields @attr@, -- @row_head_columns@, @head@, and @body@. pushTableBody :: LuaError e => Pusher e TableBody@@ -98,32 +94,8 @@   $ \idx -> TableBody   <$!> peekFieldRaw peekAttr "attr" idx   <*>  peekFieldRaw (fmap RowHeadColumns . peekIntegral) "row_head_columns" idx-  <*>  peekFieldRaw (peekList peekRow) "head" idx-  <*>  peekFieldRaw (peekList peekRow) "body" idx---- | 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)---- | 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"-  . peekPair peekAttr (peekList peekRow)+  <*>  peekFieldRaw (peekList peekRowFuzzy) "head" idx+  <*>  peekFieldRaw (peekList peekRowFuzzy) "body" idx  -- | Add a value to the table at the top of the stack at a string-index. addField :: LuaError e => Name -> LuaE e () -> LuaE e ()
+ src/Text/Pandoc/Lua/Topdown.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{- |+Module      : Text.Pandoc.Lua.Topdown+Copyright   : © 2012-2021 John MacFarlane,+              © 2017-2021 Albert Krewinkel+License     : GNU GPL, version 2 or above+Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Walk documents in a filter-suitable way, descending from the root+towards the leaves.+-}+module Text.Pandoc.Lua.Topdown+  ( TraversalNode (..)+  , Topdown (..)+  , TraversalControl (..)+  )+where++import Control.Monad ((>=>))+import Text.Pandoc.Definition+import Text.Pandoc.Lua.Walk+import Text.Pandoc.Walk++-- | Helper type to do a preorder traversal of a subtree.+data TraversalNode+  = TBlock Block+  | TBlocks [Block]+  | TInline Inline+  | TInlines [Inline]++-- | Type used to traverse a 'Pandoc' AST from top to bottom, i.e.,+-- processing the root element first and then continue towards the+-- leaves depth-first. Aborts the descend if 'topdownControl' is 'Stop'.+data Topdown = Topdown+  { topdownControl :: TraversalControl+  , topdownNode :: TraversalNode+  }++-- | Extracts a list of 'Inline' elements from a 'TraversalNode'.+-- WARNING: This is a partial function and will throw an error if the+-- node contains a 'Block' or a list of 'Block's.+nodeInlines :: TraversalNode -> [Inline]+nodeInlines = \case+  TInlines xs -> xs+  TInline x   -> [x]+  _            -> error $ "The 'impossible' has happened."+                       ++ "Please report this as a bug"++-- | Extracts a list of 'Block' elements from a 'TraversalNode'.+nodeBlocks :: TraversalNode -> [Block]+nodeBlocks = \case+  TBlocks xs  -> xs+  TBlock x    -> [x]+  TInlines xs -> [Plain xs]+  TInline x   -> [Plain [x]]++-- | Creates a topdown-walking function for a list of elements.+walkTopdownM :: (Monad m, Walkable Topdown a)+             => ([a] -> TraversalNode)+             -> (a -> TraversalNode)+             -> (TraversalNode -> [a])+             -> (Topdown -> m Topdown)+             -> [a] -> m [a]+walkTopdownM mkListNode mkElemNode nodeToList f =+  f . Topdown Continue . mkListNode >=> \case+    Topdown Stop     node -> return $ nodeToList node+    Topdown Continue node -> mconcat <$>+      traverse (f . Topdown Continue . mkElemNode >=> \case+                   Topdown Stop     node' -> return $ nodeToList node'+                   Topdown Continue node' -> traverse (walkM f) $+                                             nodeToList node')+               (nodeToList node)++-- | Creates a topdown-query function for a list of elements.+queryTopdown :: (Monoid a, Walkable Topdown b)+             => ([b] -> TraversalNode)+             -> (Topdown -> a) -> [b] -> a+queryTopdown mkListNode f xs =+  f (Topdown Continue $ mkListNode xs) <> mconcat (map (query f) xs)++instance {-# OVERLAPPING #-} Walkable Topdown [Block] where+  walkM = walkTopdownM TBlocks TBlock nodeBlocks+  query = queryTopdown TBlocks++instance {-# OVERLAPPING #-} Walkable Topdown [Inline] where+  walkM = walkTopdownM TInlines TInline nodeInlines+  query = queryTopdown TInlines++instance Walkable Topdown Block where+  walkM = walkBlockM+  query = queryBlock++instance Walkable Topdown Inline where+  walkM = walkInlineM+  query = queryInline++instance Walkable Topdown Pandoc where+  walkM = walkPandocM+  query = queryPandoc++instance Walkable Topdown Citation where+  walkM = walkCitationM+  query = queryCitation++instance Walkable Topdown Row where+  walkM = walkRowM+  query = queryRow++instance Walkable Topdown TableHead where+  walkM = walkTableHeadM+  query = queryTableHead++instance Walkable Topdown TableBody where+  walkM = walkTableBodyM+  query = queryTableBody++instance Walkable Topdown TableFoot where+  walkM = walkTableFootM+  query = queryTableFoot++instance Walkable Topdown Caption where+  walkM = walkCaptionM+  query = queryCaption++instance Walkable Topdown Cell where+  walkM = walkCellM+  query = queryCell++instance Walkable Topdown MetaValue where+  walkM = walkMetaValueM+  query = queryMetaValue++instance Walkable Topdown Meta where+  walkM f (Meta metamap) = Meta <$> walkM f metamap+  query f (Meta metamap) = query f metamap
src/Text/Pandoc/Lua/Walk.hs view
@@ -15,10 +15,13 @@ module Text.Pandoc.Lua.Walk   ( SpliceList (..)   , Walkable+  , TraversalControl (..)   , walkSplicing   , walkStraight   , applyStraight   , applySplicing+  , applyStraightFunction+  , applySplicingFunction   ) where @@ -51,7 +54,7 @@       pure     Just fn ->       -- Walk the element with the filter function.-      walkM $ applyStraightFunction fn pushElement peekElement+      walkM $ fmap fst . applyStraightFunction fn pushElement peekElement  -- | Applies a filter on an element. The element is pushed to the stack -- via the given pusher and calls the filter function with that value,@@ -64,7 +67,7 @@     Nothing ->       -- There is no filter function, do nothing.       pure x-    Just fn -> do+    Just fn -> fst <$>       -- Apply the function       applyStraightFunction fn pushElement peekElement x @@ -74,13 +77,15 @@ -- on the stack. applyStraightFunction :: LuaError e                       => FilterFunction -> Pusher e a -> Peeker e a-                      -> a -> LuaE e a+                      -> a -> LuaE e (a, TraversalControl) applyStraightFunction fn pushElement peekElement x = do   pushFilterFunction fn   pushElement x-  callWithTraceback 1 1-  forcePeek . (`lastly` pop 1) $-    (x <$ peekNil top) <|> peekElement top+  callWithTraceback 1 2+  forcePeek . (`lastly` pop 2) $+    (,)+    <$> ((x <$ peekNil (nth 2)) <|> peekElement (nth 2))+    <*> peekTraversalControl top  -- -- Splicing@@ -118,7 +123,7 @@     Nothing ->       -- There is no filter function, do nothing.       pure [x]-    Just fn -> do+    Just fn -> fst <$>       -- Apply the function       applySplicingFunction fn pushElement peekElements x @@ -128,14 +133,17 @@ -- on the stack. applySplicingFunction :: LuaError e                       => FilterFunction -> Pusher e a -> Peeker e [a]-                      -> a -> LuaE e [a]+                      -> a -> LuaE e ([a], TraversalControl) applySplicingFunction fn pushElement peekElements x = do   pushFilterFunction fn   pushElement x-  callWithTraceback 1 1-  forcePeek . (`lastly` pop 1) $ liftLua (ltype top) >>= \case-    TypeNil -> pure [x]  -- function returned `nil`, keep original value-    _       -> peekElements top+  callWithTraceback 1 2+  forcePeek . (`lastly` pop 2) $+    (,)+    <$> (liftLua (ltype (nth 2)) >>= \case+            TypeNil -> pure [x]  -- function returned `nil`, keep original value+            _       -> peekElements (nth 2))+    <*> peekTraversalControl top  -- -- Helper@@ -158,3 +166,13 @@   remove tracebackIdx   when (result /= OK)     throwErrorAsException++data TraversalControl = Continue | Stop++-- | Retrieves a Traversal control value: @nil@ or a truthy value+-- translate to 'Continue', @false@ is treated to mean 'Stop'.+peekTraversalControl :: Peeker e TraversalControl+peekTraversalControl idx = (Continue <$ peekNil idx)+  <|> (liftLua (toboolean top) >>= \case+          True -> pure Continue+          False -> pure Stop)
test/test-block.lua view
@@ -246,20 +246,20 @@     group 'Table' {       test('access Attr via property `attr`', function ()         local caption = {long = {Plain 'cap'}}-        local tbl = Table(caption, {}, {{}, {}}, {}, {{}, {}},+        local tbl = Table(caption, {}, TableHead(), {}, TableFoot(),                                  {'my-tbl', {'a'}})         assert.are_equal(tbl.attr, Attr{'my-tbl', {'a'}})          tbl.attr = Attr{'my-other-tbl', {'b'}}         assert.are_equal(-          Table(caption, {}, {{}, {}}, {}, {{}, {}},+          Table(caption, {}, TableHead(), {}, TableFoot(),                        {'my-other-tbl', {'b'}}),           tbl         )       end),       test('access caption via property `caption`', function ()         local caption = {long = {Plain 'cap'}}-        local tbl = Table(caption, {}, {{}, {}}, {}, {{}, {}})+        local tbl = Table(caption, {}, TableHead(), {}, TableFoot())         assert.are_same(tbl.caption, {long = {Plain 'cap'}})          tbl.caption.short = 'brief'@@ -270,13 +270,13 @@           long = {Plain 'extended'}         }         assert.are_equal(-          Table(new_caption, {}, {{}, {}}, {}, {{}, {}}),+          Table(new_caption, {}, TableHead(), {}, TableFoot()),           tbl         )       end),       test('access column specifiers via property `colspecs`', function ()         local colspecs = {{AlignCenter, 1}}-        local tbl = Table({long = {}}, colspecs, {{}, {}}, {}, {{}, {}})+        local tbl = Table({long = {}}, colspecs, TableHead(), {}, TableFoot())         assert.are_same(tbl.colspecs, colspecs)          tbl.colspecs[1][1] = AlignRight@@ -284,33 +284,39 @@          local new_colspecs = {{AlignRight}}         assert.are_equal(-          Table({long = {}}, new_colspecs, {{}, {}}, {}, {{}, {}}),+          Table({long = {}}, new_colspecs, TableHead(), {}, TableFoot()),           tbl         )       end),       test('access table head via property `head`', function ()-        local head = {Attr{'tbl-head'}, {}}-        local tbl = Table({long = {}}, {}, head, {}, {{}, {}})+        local head = TableHead({Row{Cell'a'}}, Attr('tbl-head'))+        local tbl = Table({long = {}}, {}, head, {}, TableFoot())         assert.are_same(tbl.head, head) -        tbl.head[1] = Attr{'table-head'}+        local new_head = head:clone()+        new_head.attr = Attr{'table-head'}+        new_head.rows = {Row{Cell{'test'}}} -        local new_head = {'table-head', {}}+        tbl.head = new_head+         assert.are_equal(-          Table({long = {}}, {}, new_head, {}, {{}, {}}),+          Table({long = {}}, {}, new_head, {}, TableFoot()),           tbl         )       end),-      test('access table head via property `head`', function ()-        local foot = {{id = 'tbl-foot'}, {}}-        local tbl = Table({long = {}}, {}, {{}, {}}, {}, foot)-        assert.are_same(tbl.foot, {Attr('tbl-foot'), {}})+      test('access table foot via property `foot`', function ()+        local foot = TableFoot({Row{Cell{'test'}}}, {id = 'tbl-foot'})+        local tbl = Table({long = {}}, {}, TableHead(), {}, foot)+        assert.are_same(tbl.foot, foot) -        tbl.foot[1] = Attr{'table-foot'}+        local new_foot = foot:clone()+        new_foot.attr = Attr{'table-foot'}+        new_foot.rows = {Row{Cell{'test'}}} -        local new_foot = {'table-foot', {}}+        tbl.foot = new_foot+         assert.are_equal(-          Table({long = {}}, {}, {{}, {}}, {}, new_foot),+          Table({long = {}}, {}, TableHead(), {}, new_foot),           tbl         )       end)@@ -448,6 +454,96 @@       }       assert.are_same(         {'Str', 'Inlines', 'Para', 'CodeBlock', 'Blocks'},+        names+      )+    end),+    test('topdown traversal works', function ()+      local names = List{}+      local tbl = Table(+        {long = {}},+        {{AlignCenter, 1}},+        TableHead{Row({Cell{'test', Para{'foo', Emph{'bar'}}}}, 'foo')},+        {},+        TableFoot()+      )+      tbl:walk{+        traverse = 'topdown',+        Blocks = function (_)+          names:insert('Blocks')+        end,+        Block = function (b)+          names:insert(b.t)+        end,+        Inline = function (i)+          names:insert(i.t)+        end,+        Inlines = function (_)+          names:insert('Inlines')+        end,+      }+      assert.are_same(+        -- Caption  Cell+        {'Blocks', 'Blocks',+         'Plain', 'Inlines', 'Str',+         'Para', 'Inlines', 'Str',+         'Emph', 'Inlines', 'Str'+        },+        names+      )+    end),+    test('truncating topdown traversal works', function ()+      local names = List{}+      local div = Div{+        Para{Emph 'a'},+        Plain{'b'},+        CodeBlock('c')+      }+      local filter+      filter = {+        traverse = 'topdown',+        Block = function (b)+          names:insert(b.t)+          if b.t == 'Para' then+            return b, false+          end+        end,+        Inline = function (i)+          names:insert(i.t)+          return i:walk(filter), false  -- continue 'manually'+        end,+      }+      div:walk(filter)+      assert.are_same(+        {'Para',  -- Emph is skipped!+         'Plain', 'Str',+         'CodeBlock',+        },+        names+      )+    end),+    test('truncating topdown traversal works in inlines', function ()+      local names = List{}+      local div = Div{+        Para{Emph 'a'},+        Plain{'b'},+      }+      div:walk {+        traverse = 'topdown',+        Block = function (b)+          names:insert(b.t)+          if b.t == 'Plain' then+            return nil, false+          end+        end,+        Emph = function (i)+          names:insert(i.t)+          return nil, false+        end,+      }+      assert.are_same(+        {'Para', 'Emph', -- Str is skipped+         'Plain',        -- Str is skipped here, too+        },         names       )     end),
test/test-inline.lua view
@@ -412,7 +412,6 @@           names:insert('Inlines')         end,       }-      print(names)       assert.are_equal(         'Str, Space, Str, Space, Str, Inlines, Para, CodeBlock, Blocks',         table.concat(names, ', ')
test/test-list.lua view
@@ -73,11 +73,19 @@         local list = List:new {19, 23, 29, 71}         assert.are_equal(23, list:find(23, 1))         assert.are_equal(23, list:find(23, 2))+        assert.are_equal(23, list:find(23, -4))         assert.is_nil(list:find(23, 3))+        assert.is_nil(list:find(23, -2))       end),       test('returns nil if element not found', function ()         assert.is_nil((List:new {18, 20, 22, 0, 24}):find('0'))       end),+      test('fails if start index is not an integer', function ()+        assert.error_matches(+          function () List:new{}:find(0, 'NaN') end,+          'number expected, got string'+        )+      end)     },      group 'find_if' {@@ -99,6 +107,17 @@         local is_zero = function (n) return n == 0 end         assert.is_nil((List:new {18, 20, 22, 24, 27}):find_if(is_zero))       end),+      test('respects start index', function ()+        local list = List:new {9, 29, 3, 71}+        assert.are_equal(71, list:find_if(function(n) return n > 10 end, 3))+        assert.are_equal(29, list:find_if(function(n) return n > 10 end, -3))+      end),+      test('fails if start index is not an integer', function ()+        assert.error_matches(+          function () List:new{}:find(0, 'NaN') end,+          'number expected, got string'+        )+      end)     },      group 'includes' {@@ -142,6 +161,14 @@         local primes = List:new {2, 3, 5, 7}         local indices = primes:map(function (x, i) return i end)         assert.are_same(List{1, 2, 3, 4}, indices)+      end),+      test('map returns a generic list', function ()+        local custom = CustomList{'α', 'β'}+        assert.are_equal(debug.getmetatable(custom).__name, 'CustomList')+        assert.are_same(+          debug.getmetatable(custom:map(tostring)).__name,+          'List'+        )       end)     }, @@ -194,22 +221,54 @@   },   group 'Operations' {     group 'concatenation' {-    test('yields a concatenated list', function ()-      assert.are_same(List {3, 4, 5, 6}, List{3, 4} .. List {5, 6})-    end),-    test('does not modify its operands', function ()-      local a = List {54, 74}-      local b = List {90, 2014}-      local result = a .. b-      assert.are_same(a, List{54, 74})-      assert.are_same(b, List{90, 2014})-    end),-    test('sets metatable of first operand on result', function ()-      local result = {1, 4} .. List{9, 16}-      assert.are_equal(nil, getmetatable(result))-      result = List{1, 4} .. {9, 16}-      assert.are_equal(List, getmetatable(result))-    end),+      test('yields a concatenated list', function ()+        assert.are_same(List {3, 4, 5, 6}, List{3, 4} .. List {5, 6})+      end),+      test('does not modify its operands', function ()+        local a = List {54, 74}+        local b = List {90, 2014}+        local result = a .. b+        assert.are_same(a, List{54, 74})+        assert.are_same(b, List{90, 2014})+      end),+      test('sets metatable of first operand on result', function ()+        local result = {1, 4} .. List{9, 16}+        assert.are_equal(nil, getmetatable(result))+        result = List{1, 4} .. {9, 16}+        assert.are_equal(List, getmetatable(result))+      end),+    },+    group 'equality' {+      test('lists are equal if all elements are equal', function ()+        assert.are_equal(+          List {5, 6, 7, 8},+          List {5, 6, 7, 8}+        )+      end),+      test('lists are not equal if their metatables are different', function ()+        assert.is_truthy(+          List {18, 20, 2, 0, 24} ~=+          CustomList {18, 20, 2, 0, 24}+        )+      end),+      test('lists are not equal if one is a plain table', function ()+        assert.is_truthy(+          List {18, 20, 2, 0, 24} ~=+          {18, 20, 2, 0, 24}+        )+      end),+      test('lists are not equal if an element differs', function ()+        assert.is_truthy(+          List {18, 20, 22, 23, 24} ~=+          List {18, 20, 22, 0, 24}+        )+      end),+      test('can compare to a string', function ()+        assert.is_truthy(+          List {'a', 'b', 'c'} ~=+          "abc"+        )+      end),     }   }, }
test/test-metavalue.lua view
@@ -11,5 +11,9 @@       assert.are_equal(type(metalist.insert), 'function')       assert.are_equal(type(metalist.remove), 'function')     end),+    test('Numbers are treated as strings', function ()+      local metalist = MetaList{5, 23, 13.37}+      assert.are_same(metalist, MetaList{'5', '23', '13.37'})+    end)   } }
test/test-pandoc-lua-marshal.hs view
@@ -28,6 +28,13 @@   listTests <- run @Lua.Exception $ do     openlibs     pushListModule *> setglobal "List"+    -- Create a custom List type with constructor "CustomList"+    pushHaskellFunction $ do+      settop 1+      newListMetatable "CustomList" (pure ())+      setmetatable (nthBottom 1)+      return 1+    setglobal "CustomList"     translateResultsFromFile "test/test-list.lua"    listAttributeTests <- run @Lua.Exception $ do@@ -53,52 +60,19 @@     translateResultsFromFile "test/test-citation.lua"    inlineTests <- run @Lua.Exception $ do-    openlibs-    pushListModule *> setglobal "List"-    register' mkAttr-    register' mkCitation-    register' mkInlines-    registerConstants (Proxy @CitationMode)-    registerConstants (Proxy @MathType)-    registerConstants (Proxy @QuoteType)-    forM_ inlineConstructors register'-    forM_ blockConstructors register'+    registerDefault     translateResultsFromFile "test/test-inline.lua"    blockTests <- run @Lua.Exception $ do-    openlibs-    pushListModule *> setglobal "List"-    register' mkAttr-    register' mkBlocks-    register' mkListAttributes-    registerConstants (Proxy @Alignment)-    registerConstants (Proxy @ListNumberStyle)-    registerConstants (Proxy @ListNumberStyle)-    forM_ inlineConstructors register'-    forM_ blockConstructors register'+    registerDefault     translateResultsFromFile "test/test-block.lua"    cellTests <- run @Lua.Exception $ do-    openlibs-    pushListModule *> setglobal "List"-    register' mkAttr-    register' mkBlocks-    register' mkCell-    register' mkListAttributes-    registerConstants (Proxy @Alignment)-    forM_ inlineConstructors register'-    forM_ blockConstructors register'+    registerDefault     translateResultsFromFile "test/test-cell.lua"    simpleTableTests <- run @Lua.Exception $ do-    openlibs-    pushListModule *> setglobal "List"-    register' mkAttr-    register' mkListAttributes-    register' mkSimpleTable-    registerConstants (Proxy @Alignment)-    forM_ inlineConstructors register'-    forM_ blockConstructors register'+    registerDefault     translateResultsFromFile "test/test-simpletable.lua"    metavalueTests <- run @Lua.Exception $ do@@ -108,12 +82,7 @@     translateResultsFromFile "test/test-metavalue.lua"    pandocTests <- run @Lua.Exception $ do-    openlibs-    pushListModule *> setglobal "List"-    register' mkMeta-    register' mkPandoc-    forM_ inlineConstructors register'-    forM_ blockConstructors register'+    registerDefault     translateResultsFromFile "test/test-pandoc.lua"    defaultMain $ testGroup "pandoc-lua-marshal"@@ -129,6 +98,31 @@     , metavalueTests     , pandocTests     ]++-- | Registers all constructors and string constants in the global+-- environment.+registerDefault :: LuaError e => LuaE e ()+registerDefault = do+  openlibs+  pushListModule *> setglobal "List"+  register' mkAttr+  register' mkBlocks+  register' mkCell+  register' mkCitation+  register' mkInlines+  register' mkListAttributes+  register' mkPandoc+  register' mkRow+  register' mkSimpleTable+  register' mkTableHead+  register' mkTableFoot+  registerConstants (Proxy @Alignment)+  registerConstants (Proxy @ListNumberStyle)+  registerConstants (Proxy @ListNumberStyle)+  registerConstants (Proxy @MathType)+  registerConstants (Proxy @QuoteType)+  forM_ inlineConstructors register'+  forM_ blockConstructors register'  register' :: LuaError e => DocumentedFunction e -> LuaE e () register' f = do
test/test-pandoc.lua view
@@ -54,5 +54,88 @@         }       )     end),+    test('default traversal is typewise, bottom-up', function ()+      local names = List{}+      local doc = Pandoc(+        Blocks{+          Div{+            Plain{Emph 'a'},+            Para{'b'},+            CodeBlock('c')+          }+        },+        { test = Blocks 'foo' }+      )+      doc:walk {+        Block = function (b)+          names:insert(b.t)+        end,+        Inline = function (i)+          names:insert(i.t)+        end,+        Pandoc = function (_)+          names:insert('Pandoc')+        end,+        Meta = function (_)+          names:insert('Meta')+        end+      }+      assert.are_same(+        { 'Str',   -- in meta value+          'Str',   -- in Emph+          'Emph',+          'Str',   -- in Para,+          'Plain', -- in meta value+          'Plain',+          'Para',+          'CodeBlock',+          'Div',+          'Meta',+          'Pandoc'+        },+        names+      )+    end),+    test('truncating topdown traversal works', function ()+      local names = List{}+      local doc = Pandoc(+        Blocks{+          Div{+            Plain{Emph 'a'},+            Para{'b'},+            CodeBlock('c')+          }+        },+        { test = Blocks 'foo' }+      )+      doc:walk {+        traverse = 'topdown',+        Block = function (b)+          names:insert(b.t)+          if b.t == 'Para' then+            return b, false+          end+        end,+        Inline = function (i)+          names:insert(i.t)+        end,+        Pandoc = function (_)+          names:insert('Pandoc')+        end,+        Meta = function (_)+          names:insert('Meta')+        end+      }+      assert.are_same(+        { 'Pandoc',+          'Meta', 'Plain', 'Str', -- Meta and meta value+          'Div',+          'Plain', 'Emph', 'Str',+          'Para',                 -- Str is skipped!+          'CodeBlock'+        },+        names+      )+    end),   } }