git.iko.soy/typeable/pandoc
on master help sign in
data/pandoc.lua: enable table-like behavior of attributes (#4080)

Attribute lists are represented as associative lists in Lua. Pure associative lists are awkward to work with. A metatable is attached to attribute lists, allowing to access and use the associative list as if the attributes were stored in as normal key-value pair in table. Note that this changes the way `pairs` works on attribute lists. Instead of producing integer keys and two-element tables, the resulting iterator function now returns the key and value of those pairs. Use `ipairs` to get the old behavior. Warning: the new iteration mechanism only works if pandoc has been compiled with Lua 5.2 or later (current default: 5.3). The `pandoc.Attr` function is altered to allow passing attributes as key-values in a normal table. This is more convenient than having to construct the associative list which is used internally. Closes #4071

commit849900c516f2aab60c675ae46dc87140165ef1da authorAlbert Krewinkel <albert+github@zeitkraut.de> dateMon 20 Nov 2017, 18:37 (9 years ago) parent97efed8
3 files changed+109−4
data/pandoc.lua+92−1 test/Tests/Lua.hs+11−3 test/lua/attr-test.lua+6
data/pandoc.lua+92−1
@@ -627,6 +627,97 @@ M.Superscript = M.Inline:create_constructor(
627627-- Helpers
628628-- @section helpers
629629
630+-- Find a value pair in a list.
631+-- @function find
632+-- @tparam table list to be searched
633+-- @param needle element to search for
634+-- @param[opt] key when non-nil, compare on this field of each list element
635+local function find (alist, needle, key)
636+ local test
637+ if key then
638+ test = function(x) return x[key] == needle end
639+ else
640+ test = function(x) return x == needle end
641+ end
642+ for i, k in ipairs(alist) do
643+ if test(k) then
644+ return i, k
645+ end
646+ end
647+ return nil
648+end
649+
650+-- Lookup a value in an associative list
651+-- @function lookup
652+-- @tparam {{key, value},...} alist associative list
653+-- @param key key for which the associated value is to be looked up
654+local function lookup(alist, key)
655+ return (select(2, find(alist, key, 1)) or {})[2]
656+end
657+
658+--- Return an iterator which returns key-value pairs of an associative list.
659+-- @function apairs
660+-- @tparam {{key, value},...} alist associative list
661+local apairs = function (alist)
662+ local i = 1
663+ local cur
664+ function nxt ()
665+ cur = rawget(alist, i)
666+ if cur then
667+ i = i + 1
668+ return cur[1], cur[2]
669+ end
670+ return nil
671+ end
672+ return nxt, nil, nil
673+end
674+
675+-- AttributeList, a metatable to allow table-like access to attribute lists
676+-- represented by associative lists.
677+local AttributeList = {
678+ __index = function (t, k)
679+ if type(k) == "number" then
680+ return rawget(t, k)
681+ else
682+ return lookup(t, k)
683+ end
684+ end,
685+
686+ __newindex = function (t, k, v)
687+ local idx, cur = find(t, k, 1)
688+ if v == nil then
689+ table.remove(t, idx)
690+ elseif cur then
691+ cur[2] = v
692+ elseif type(k) == "number" then
693+ rawset(t, k, v)
694+ else
695+ rawset(t, #t + 1, {k, v})
696+ end
697+ end,
698+
699+ __pairs = apairs
700+}
701+
702+-- convert a table to an associative list. The order of key-value pairs in the
703+-- alist is undefined. The table should either contain no numeric keys or
704+-- already be an associative list.
705+-- @tparam table associative list or table without numeric keys.
706+-- @treturn table associative list
707+local to_alist = function (tbl)
708+ if #tbl ~= 0 or next(tbl) == nil then
709+ -- probably already an alist
710+ return tbl
711+ end
712+ local alist = {}
713+ local i = 1
714+ for k, v in pairs(tbl) do
715+ alist[i] = {k, v}
716+ i = i + 1
717+ end
718+ return alist
719+end
720+
630721-- Attr
631722M.Attr = {}
632723M.Attr._field_names = {identifier = 1, classes = 2, attributes = 3}
@@ -639,7 +730,7 @@ M.Attr._field_names = {identifier = 1, classes = 2, attributes = 3}
639730M.Attr.__call = function(t, identifier, classes, attributes)
640731 identifier = identifier or ''
641732 classes = classes or {}
642− attributes = attributes or {}
733+ attributes = setmetatable(to_alist(attributes or {}), AttributeList)
643734 local attr = {identifier, classes, attributes}
644735 setmetatable(attr, t)
645736 return attr
test/Tests/Lua.hs+11−3
@@ -7,9 +7,9 @@ import Test.Tasty (TestTree, localOption)
77import Test.Tasty.HUnit (Assertion, assertEqual, testCase)
88import Test.Tasty.QuickCheck (QuickCheckTests (..), ioProperty, testProperty)
99import Text.Pandoc.Arbitrary ()
10−import Text.Pandoc.Builder (bulletList, doc, doubleQuoted, emph, header,
11− linebreak, para, plain, rawBlock, singleQuoted,
12− space, str, strong, (<>))
10+import Text.Pandoc.Builder (bulletList, divWith, doc, doubleQuoted, emph,
11+ header, linebreak, para, plain, rawBlock,
12+ singleQuoted, space, str, strong, (<>))
1313import Text.Pandoc.Class (runIOorExplode)
1414import Text.Pandoc.Definition (Block, Inline, Meta, Pandoc)
1515import Text.Pandoc.Lua
@@ -83,6 +83,14 @@ tests = map (localOption (QuickCheckTests 20))
8383 "uppercase-header.lua"
8484 (doc $ header 1 "les états-unis" <> para "text")
8585 (doc $ header 1 "LES ÉTATS-UNIS" <> para "text")
86+
87+ , testCase "Attribute lists are convenient to use" $
88+ let kv_before = [("one", "1"), ("two", "2"), ("three", "3")]
89+ kv_after = [("one", "eins"), ("three", "3"), ("five", "5")]
90+ in assertFilterConversion "Attr doesn't behave as expected"
91+ "attr-test.lua"
92+ (doc $ divWith ("", [], kv_before) (para "nil"))
93+ (doc $ divWith ("", [], kv_after) (para "nil"))
8694 ]
8795
8896assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
test/lua/attr-test.luanew file+6
@@ -0,0 +1,6 @@
1+function Div (div)
2+ div.attributes.five = ("%d"):format(div.attributes.two + div.attributes.three)
3+ div.attributes.two = nil
4+ div.attributes.one = "eins"
5+ return div
6+end