From d7cab5198269fbbdbc40f54a2ad7aeb83fee619f Mon Sep 17 00:00:00 2001
From: Albert Krewinkel <albert@zeitkraut.de>
Date: Tue, 21 Dec 2021 09:40:23 +0100
Subject: [PATCH] Lua: add new library function `pandoc.utils.type`.

The function behaves like the default `type` function from Lua's
standard library, but is aware of pandoc userdata types. A typical
use-case would be to determine the type of a metadata value.
---
 doc/lua-filters.md                  | 36 +++++++++++++++++++++++++-
 src/Text/Pandoc/Lua/Module/Utils.hs | 12 +++++++++
 test/lua/module/pandoc-utils.lua    | 40 +++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/doc/lua-filters.md b/doc/lua-filters.md
index 901fd6be8..e5ea90104 100644
--- a/doc/lua-filters.md
+++ b/doc/lua-filters.md
@@ -1863,7 +1863,8 @@ Fields:
 Column alignment and width specification for a single table
 column.
 
-This is a pair with the following components:
+This is a pair, i.e., a plain table, with the following
+components:
 
 1. cell alignment ([Alignment]).
 2. table column width, as a fraction of the total table width
@@ -3507,6 +3508,39 @@ Usage:
     -- create normal table block again
     table = pandoc.utils.from_simple_table(simple)
 
+### type {#pandoc.utils.type}
+
+`type (value)`
+
+Pandoc-friendly version of Lua's default `type` function,
+returning the type of a value. This function works with all types
+listed in section [Lua type reference][], except if noted
+otherwise.
+
+The function works by checking the metafield `__name`. If the
+argument has a string-valued metafield `__name`, then it returns
+that string. Otherwise it behaves just like the normal `type`
+function.
+
+Parameters:
+
+`value`
+:   any Lua value
+
+Returns:
+
+-   type of the given value (string)
+
+Usage:
+
+    -- Prints one of 'string', 'boolean', 'Inlines', 'Blocks',
+    -- 'table', and 'nil', corresponding to the Haskell constructors
+    -- MetaString, MetaBool, MetaInlines, MetaBlocks, MetaMap,
+    -- and an unset value, respectively.
+    function Meta (meta)
+      print('type of metavalue `author`:', pandoc.utils.type(meta.author))
+    end
+
 # Module pandoc.mediabag
 
 The `pandoc.mediabag` module allows accessing pandoc's media
diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs
index 439a9a50b..c1bb42410 100644
--- a/src/Text/Pandoc/Lua/Module/Utils.hs
+++ b/src/Text/Pandoc/Lua/Module/Utils.hs
@@ -21,6 +21,7 @@ import Control.Applicative ((<|>))
 import Control.Monad ((<$!>))
 import Data.Data (showConstr, toConstr)
 import Data.Default (def)
+import Data.Maybe (fromMaybe)
 import Data.Version (Version)
 import HsLua as Lua
 import HsLua.Class.Peekable (PeekError)
@@ -145,6 +146,17 @@ documentedModule = Module
       <#> parameter peekTable "Block" "tbl" "a table"
       =#> functionResult pushSimpleTable "SimpleTable" "SimpleTable object"
       #? "Converts a table into an old/simple table."
+
+    , defun "type"
+      ### (\idx -> getmetafield idx "__name" >>= \case
+              TypeString -> fromMaybe mempty <$> tostring top
+              _ -> ltype idx >>= typename)
+      <#> parameter pure "any" "object" ""
+      =#> functionResult pushByteString "string" "type of the given value"
+    #? ("Pandoc-friendly version of Lua's default `type` function, " <>
+        "returning the type of a value. If the argument has a " <>
+        "string-valued metafield `__name`, then it gives that string. " <>
+        "Otherwise it behaves just like the normal `type` function.")
     ]
   }
 
diff --git a/test/lua/module/pandoc-utils.lua b/test/lua/module/pandoc-utils.lua
index 7a43e9286..104adfe4c 100644
--- a/test/lua/module/pandoc-utils.lua
+++ b/test/lua/module/pandoc-utils.lua
@@ -116,6 +116,46 @@ return {
     end)
   },
 
+  group 'type' {
+    test('nil', function ()
+      assert.are_equal(utils.type(nil), 'nil')
+    end),
+    test('boolean', function ()
+      assert.are_equal(utils.type(true), 'boolean')
+      assert.are_equal(utils.type(false), 'boolean')
+    end),
+    test('number', function ()
+      assert.are_equal(utils.type(5), 'number')
+      assert.are_equal(utils.type(-3.02), 'number')
+    end),
+    test('string', function ()
+      assert.are_equal(utils.type(''), 'string')
+      assert.are_equal(utils.type('asdf'), 'string')
+    end),
+    test('plain table', function ()
+      assert.are_equal(utils.type({}), 'table')
+    end),
+    test('List', function ()
+      assert.are_equal(utils.type(pandoc.List{}), 'List')
+    end),
+    test('Inline', function ()
+      assert.are_equal(utils.type(pandoc.Str 'a'), 'Inline')
+      assert.are_equal(utils.type(pandoc.Emph 'emphasized'), 'Inline')
+    end),
+    test('Inlines', function ()
+      assert.are_equal(utils.type(pandoc.Inlines{pandoc.Str 'a'}), 'Inlines')
+      assert.are_equal(utils.type(pandoc.Inlines{pandoc.Emph 'b'}), 'Inlines')
+    end),
+    test('Blocks', function ()
+      assert.are_equal(utils.type(pandoc.Para 'a'), 'Block')
+      assert.are_equal(utils.type(pandoc.CodeBlock 'true'), 'Block')
+    end),
+    test('Inlines', function ()
+      assert.are_equal(utils.type(pandoc.Blocks{'a'}), 'Blocks')
+      assert.are_equal(utils.type(pandoc.Blocks{pandoc.CodeBlock 'b'}), 'Blocks')
+    end),
+  },
+
   group 'to_simple_table' {
     test('convertes Table', function ()
       function simple_cell (blocks)