diff --git a/.hlint.yaml b/.hlint.yaml index 5c262c3d0..d68823cba 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -78,6 +78,7 @@ - Tests.Writers.Native - Text.Pandoc.Extensions - Text.Pandoc.Lua.Marshaling.Version + - Text.Pandoc.Lua.Module.Utils - Text.Pandoc.Readers.Odt.ContentReader - Text.Pandoc.Readers.Odt.Namespaces diff --git a/data/pandoc.lua b/data/pandoc.lua index d031bf5d0..35ca20a84 100644 --- a/data/pandoc.lua +++ b/data/pandoc.lua @@ -1058,6 +1058,30 @@ M.ListAttributes.behavior.__pairs = function(t) return make_next_function(fields), t, nil end +-- +-- Legacy and compatibility types +-- + +--- Creates a simple (old style) table element. +-- @function SimpleTable +-- @tparam {Inline,...} caption table caption +-- @tparam {AlignDefault|AlignLeft|AlignRight|AlignCenter,...} aligns alignments +-- @tparam {int,...} widths column widths +-- @tparam {Block,...} headers header row +-- @tparam {{Block,...}} rows table rows +-- @treturn Block table element +M.SimpleTable = function(caption, aligns, widths, headers, rows) + return { + caption = ensureInlineList(caption), + aligns = List:new(aligns), + widths = List:new(widths), + headers = List:new(headers), + rows = List:new(rows), + tag = "SimpleTable", + t = "SimpleTable", + } +end + ------------------------------------------------------------------------ -- Constants diff --git a/doc/lua-filters.md b/doc/lua-filters.md index 1cdad7391..cc728eeec 100644 --- a/doc/lua-filters.md +++ b/doc/lua-filters.md @@ -1744,6 +1744,36 @@ table into a List. A pandoc log message. Objects have no fields, but can be converted to a string via `tostring`. +## SimpleTable {#type-simpletable} + +A simple table is a table structure which resembles the old (pre +pandoc 2.10) Table type. Bi-directional conversion from and to +[Tables](#type-table) is possible with the +[`pandoc.utils.to_simple_table`](#pandoc.utils.to_simple_table) +and +[`pandoc.utils.from_simple_table`](#pandoc.utils.from_simple_table) +function, respectively. Instances of this type can also be created +directly with the [`pandoc.SimpleTable`](#pandoc.simpletable) +constructor. + +Fields: + +`caption`: +: [List] of [Inlines] + +`aligns`: +: column alignments ([List] of [Alignments](#type-alignment)) + +`widths`: +: column widths; a ([List] of numbers) + +`headers`: +: table header row ([List] of lists of [Blocks]) + +`rows`: +: table rows ([List] of rows, where a row is a list of lists of + [Blocks]) + ## Version {#type-version} A version object. This represents a software version like @@ -1816,6 +1846,8 @@ Usage: [Pandoc]: #type-pandoc [Para]: #type-para [Rows]: #type-row +[SimpleTable]: #type-simpletable +[Table]: #type-table [TableBody]: #type-tablebody [TableFoot]: #type-tablefoot [TableHead]: #type-tablehead @@ -2491,6 +2523,51 @@ format, and functions to filter and modify a subtree. Returns: [ListAttributes](#type-listattributes) object +## Legacy types + +[`SimpleTable (caption, aligns, widths, headers, rows)`]{#pandoc.simpletable} + +: Creates a simple table resembling the old (pre pandoc 2.10) + table type. + + Parameters: + + `caption`: + : [List] of [Inlines] + + `aligns`: + : column alignments ([List] of [Alignments](#type-alignment)) + + `widths`: + : column widths; a ([List] of numbers) + + `headers`: + : table header row ([List] of lists of [Blocks]) + + `rows`: + : table rows ([List] of rows, where a row is a list of lists + of [Blocks]) + + Returns: [SimpleTable] object + + Usage: + + local caption = "Overview" + local aligns = {pandoc.AlignDefault, pandoc.AlignDefault} + local widths = {0, 0} -- let pandoc determine col widths + local headers = {"Language", "Typing"} + local rows = { + {{pandoc.Plain "Haskell"}, {pandoc.Plain "static"}}, + {{pandoc.Plain "Lua"}, {pandoc.Plain "Dynamic"}}, + } + simple_table = pandoc.SimpleTable( + caption, + aligns, + widths, + headers, + rows + ) + ## Constants [`AuthorInText`]{#pandoc.authorintext} @@ -2753,6 +2830,26 @@ Returns: - Whether the two objects represent the same element (boolean) +### from\_simple\_table {#pandoc.utils.from_simple_table} + +`from_simple_table (table)` + +Creates a [Table] block element from a [SimpleTable]. This is +useful for dealing with legacy code which was written for pandoc +versions older than 2.10. + +Returns: + +- table block element ([Table]) + +Usage: + + local simple = pandoc.SimpleTable(table) + -- modify, using pre pandoc 2.10 methods + simple.caption = pandoc.SmallCaps(simple.caption) + -- create normal table block again + table = pandoc.utils.from_simple_table(simple) + ### make\_sections {#pandoc.utils.make_sections} `make_sections (number_sections, base_level, blocks)` @@ -2872,6 +2969,24 @@ Usage: local pandoc_birth_year = to_roman_numeral(2006) -- pandoc_birth_year == 'MMVI' +### to\_simple\_table {#pandoc.utils.to_simple_table} + +`to_simple_table (table)` + +Creates a [SimpleTable] out of a [Table] block. + +Returns: + +- a simple table object ([SimpleTable]) + +Usage: + + local simple = pandoc.utils.to_simple_table(table) + -- modify, using pre pandoc 2.10 methods + simple.caption = pandoc.SmallCaps(simple.caption) + -- create normal table block again + table = pandoc.utils.from_simple_table(simple) + # Module pandoc.mediabag The `pandoc.mediabag` module allows accessing pandoc's media diff --git a/pandoc.cabal b/pandoc.cabal index 345a4f827..e0ba97df8 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -633,6 +633,7 @@ library Text.Pandoc.Lua.Marshaling.MediaBag, Text.Pandoc.Lua.Marshaling.PandocError, Text.Pandoc.Lua.Marshaling.ReaderOptions, + Text.Pandoc.Lua.Marshaling.SimpleTable, Text.Pandoc.Lua.Marshaling.Version, Text.Pandoc.Lua.Module.MediaBag, Text.Pandoc.Lua.Module.Pandoc, diff --git a/src/Text/Pandoc/Lua/Init.hs b/src/Text/Pandoc/Lua/Init.hs index a5e513a1f..e89e9d6e0 100644 --- a/src/Text/Pandoc/Lua/Init.hs +++ b/src/Text/Pandoc/Lua/Init.hs @@ -80,6 +80,7 @@ putConstructorsInRegistry = liftPandocLua $ do putInReg "Attr" -- used for Attr type alias putInReg "ListAttributes" -- used for ListAttributes type alias putInReg "List" -- pandoc.List + putInReg "SimpleTable" -- helper for backward-compatible table handling where constrsToReg :: Data a => a -> Lua () constrsToReg = mapM_ (putInReg . showConstr) . dataTypeConstrs . dataTypeOf diff --git a/src/Text/Pandoc/Lua/Marshaling/AST.hs b/src/Text/Pandoc/Lua/Marshaling/AST.hs index 679dd1f46..c889618c4 100644 --- a/src/Text/Pandoc/Lua/Marshaling/AST.hs +++ b/src/Text/Pandoc/Lua/Marshaling/AST.hs @@ -260,7 +260,7 @@ instance Peekable TableBody where return $ TableBody attr (RowHeadColumns rowHeadColumns) head' body instance Pushable TableHead where - push (TableHead attr cells) = Lua.push (attr, cells) + push (TableHead attr rows) = Lua.push (attr, rows) instance Peekable TableHead where peek = fmap (uncurry TableHead) . Lua.peek diff --git a/src/Text/Pandoc/Lua/Marshaling/SimpleTable.hs b/src/Text/Pandoc/Lua/Marshaling/SimpleTable.hs new file mode 100644 index 000000000..98fa1efa4 --- /dev/null +++ b/src/Text/Pandoc/Lua/Marshaling/SimpleTable.hs @@ -0,0 +1,59 @@ +{- | + Module : Text.Pandoc.Lua.Marshaling.SimpleTable + Copyright : © 2020 Albert Krewinkel + License : GNU GPL, version 2 or above + + Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de> + Stability : alpha + +Definition and marshaling of the 'SimpleTable' data type used as a +convenience type when dealing with tables. +-} +module Text.Pandoc.Lua.Marshaling.SimpleTable + ( SimpleTable (..) + , peekSimpleTable + , pushSimpleTable + ) + where + +import Foreign.Lua (Lua, Peekable, Pushable, StackIndex) +import Text.Pandoc.Definition +import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor, rawField) +import Text.Pandoc.Lua.Marshaling.AST () + +import qualified Foreign.Lua as Lua + +-- | A simple (legacy-style) table. +data SimpleTable = SimpleTable + { simpleTableCaption :: [Inline] + , simpleTableAlignments :: [Alignment] + , simpleTableColumnWidths :: [Double] + , simpleTableHeader :: [[Block]] + , simpleTableBody :: [[[Block]]] + } + +instance Pushable SimpleTable where + push = pushSimpleTable + +instance Peekable SimpleTable where + peek = peekSimpleTable + +-- | Push a simple table to the stack by calling the +-- @pandoc.SimpleTable@ constructor. +pushSimpleTable :: SimpleTable -> Lua () +pushSimpleTable tbl = pushViaConstructor "SimpleTable" + (simpleTableCaption tbl) + (simpleTableAlignments tbl) + (simpleTableColumnWidths tbl) + (simpleTableHeader tbl) + (simpleTableBody tbl) + +-- | Retrieve a simple table from the stack. +peekSimpleTable :: StackIndex -> Lua SimpleTable +peekSimpleTable idx = defineHowTo "get SimpleTable" $ + SimpleTable + <$> rawField idx "caption" + <*> rawField idx "aligns" + <*> rawField idx "widths" + <*> rawField idx "headers" + <*> rawField idx "rows" diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs index 988489a2a..7595b9c0f 100644 --- a/src/Text/Pandoc/Lua/Module/Utils.hs +++ b/src/Text/Pandoc/Lua/Module/Utils.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {- | Module : Text.Pandoc.Lua.Module.Utils @@ -7,7 +8,7 @@ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de> Stability : alpha -Utility module for lua, exposing internal helper functions. +Utility module for Lua, exposing internal helper functions. -} module Text.Pandoc.Lua.Module.Utils ( pushModule @@ -15,13 +16,17 @@ module Text.Pandoc.Lua.Module.Utils import Control.Applicative ((<|>)) import Control.Monad.Catch (try) +import Data.Data (showConstr, toConstr) import Data.Default (def) import Data.Version (Version) -import Foreign.Lua (Peekable, Lua, NumResults) -import Text.Pandoc.Definition ( Pandoc, Meta, MetaValue (..), Block, Inline - , Citation, Attr, ListAttributes) +import Foreign.Lua (Peekable, Lua, NumResults (..)) +import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError) import Text.Pandoc.Lua.Marshaling () +import Text.Pandoc.Lua.Marshaling.SimpleTable + ( SimpleTable (..) + , pushSimpleTable + ) import Text.Pandoc.Lua.PandocLua (PandocLua, addFunction, liftPandocLua) import qualified Data.Digest.Pure.SHA as SHA @@ -31,19 +36,22 @@ import qualified Foreign.Lua as Lua import qualified Text.Pandoc.Builder as B import qualified Text.Pandoc.Filter.JSON as JSONFilter import qualified Text.Pandoc.Shared as Shared +import qualified Text.Pandoc.Writers.Shared as Shared --- | Push the "pandoc.utils" module to the lua stack. +-- | Push the "pandoc.utils" module to the Lua stack. pushModule :: PandocLua NumResults pushModule = do liftPandocLua Lua.newtable addFunction "blocks_to_inlines" blocksToInlines addFunction "equals" equals + addFunction "from_simple_table" from_simple_table addFunction "make_sections" makeSections addFunction "normalize_date" normalizeDate addFunction "run_json_filter" runJSONFilter addFunction "sha1" sha1 addFunction "stringify" stringify addFunction "to_roman_numeral" toRomanNumeral + addFunction "to_simple_table" to_simple_table addFunction "Version" (return :: Version -> Lua Version) return 1 @@ -131,6 +139,37 @@ instance Peekable AstElement where Left (_ :: PandocError) -> Lua.throwMessage "Expected an AST element, but could not parse value as such." +-- | Converts an old/simple table into a normal table block element. +from_simple_table :: SimpleTable -> Lua NumResults +from_simple_table (SimpleTable capt aligns widths head' body) = do + Lua.push $ Table + nullAttr + (Caption Nothing [Plain capt]) + (zipWith (\a w -> (a, toColWidth w)) aligns widths) + (TableHead nullAttr [blockListToRow head']) + [TableBody nullAttr 0 [] $ map blockListToRow body] + (TableFoot nullAttr []) + return (NumResults 1) + where + blockListToRow :: [[Block]] -> Row + blockListToRow = Row nullAttr . map (B.simpleCell . B.fromList) + + toColWidth :: Double -> ColWidth + toColWidth 0 = ColWidthDefault + toColWidth w = ColWidth w + +-- | Converts a table into an old/simple table. +to_simple_table :: Block -> Lua NumResults +to_simple_table = \case + Table _attr caption specs thead tbodies tfoot -> do + let (capt, aligns, widths, headers, rows) = + Shared.toLegacyTable caption specs thead tbodies tfoot + pushSimpleTable $ SimpleTable capt aligns widths headers rows + return (NumResults 1) + blk -> + Lua.throwMessage $ + "Expected Table, got " <> showConstr (toConstr blk) <> "." + -- | Convert a number < 4000 to uppercase roman numeral. toRomanNumeral :: Lua.Integer -> PandocLua T.Text toRomanNumeral = return . Shared.toRomanNumeral . fromIntegral diff --git a/src/Text/Pandoc/Lua/Util.hs b/src/Text/Pandoc/Lua/Util.hs index c6639e94c..fbd013801 100644 --- a/src/Text/Pandoc/Lua/Util.hs +++ b/src/Text/Pandoc/Lua/Util.hs @@ -80,7 +80,7 @@ instance (Pushable a, PushViaCall b) => PushViaCall (a -> b) where pushViaCall :: PushViaCall a => String -> a pushViaCall fn = pushViaCall' fn (return ()) 0 --- | Call a pandoc element constructor within lua, passing all given arguments. +-- | Call a pandoc element constructor within Lua, passing all given arguments. pushViaConstructor :: PushViaCall a => String -> a pushViaConstructor pandocFn = pushViaCall ("pandoc." ++ pandocFn) diff --git a/test/lua/module/pandoc-utils.lua b/test/lua/module/pandoc-utils.lua index 963e70686..9bd903f2d 100644 --- a/test/lua/module/pandoc-utils.lua +++ b/test/lua/module/pandoc-utils.lua @@ -90,4 +90,70 @@ return { assert.is_falsy(pcall(utils.to_roman_numeral, 'not a number')) end) }, + + group 'to_simple_table' { + test('convertes Table', function () + function simple_cell (blocks) + return { + attr = pandoc.Attr(), + alignment = "AlignDefault", + contents = blocks, + col_span = 1, + row_span = 1, + } + end + local tbl = pandoc.Table( + {long = {pandoc.Plain { + pandoc.Str "the", pandoc.Space(), pandoc.Str "caption"}}}, + {{pandoc.AlignDefault, nil}}, + {pandoc.Attr(), {{pandoc.Attr(), {simple_cell{pandoc.Plain "head1"}}}}}, + {{ + attr = pandoc.Attr(), + body = {{pandoc.Attr(), {simple_cell{pandoc.Plain "cell1"}}}}, + head = {}, + row_head_columns = 0 + }}, + {pandoc.Attr(), {}}, + pandoc.Attr() + ) + local stbl = utils.to_simple_table(tbl) + assert.are_equal('SimpleTable', stbl.t) + assert.are_equal('head1', utils.stringify(stbl.headers[1])) + assert.are_equal('cell1', utils.stringify(stbl.rows[1][1])) + assert.are_equal('the caption', utils.stringify(pandoc.Span(stbl.caption))) + end), + test('fails on para', function () + assert.is_falsy(pcall(utils.to_simple_table, pandoc.Para "nope")) + end), + }, + group 'from_simple_table' { + test('converts SimpleTable to Table', function () + local caption = {pandoc.Str "Overview"} + local aligns = {pandoc.AlignDefault, pandoc.AlignDefault} + local widths = {0, 0} -- let pandoc determine col widths + local headers = { + {pandoc.Plain "Language"}, + {pandoc.Plain "Typing"} + } + local rows = { + {{pandoc.Plain "Haskell"}, {pandoc.Plain "static"}}, + {{pandoc.Plain "Lua"}, {pandoc.Plain "Dynamic"}}, + } + local simple_table = pandoc.SimpleTable( + caption, + aligns, + widths, + headers, + rows + ) + local tbl = utils.from_simple_table(simple_table) + assert.are_equal("Table", tbl.t) + assert.are_same( + {pandoc.Plain(caption)}, + tbl.caption.long + ) + -- reversible + assert.are_same(simple_table, utils.to_simple_table(tbl)) + end), + } }