From 4ede11a75f37bb35d384fc9ce95231671e18acf3 Mon Sep 17 00:00:00 2001
From: Albert Krewinkel <albert@zeitkraut.de>
Date: Sat, 11 Jan 2020 20:35:56 +0100
Subject: [PATCH] Lua: add methods `insert`, `remove`, and `sort` to
 pandoc.List

The functions `table.insert`, `table.remove`, and `table.sort` are added
to pandoc.List elements.  They can be used as methods, e.g.

    local numbers = pandoc.List {2, 3, 1}
    numbers:sort()     -- numbers is now {1, 2, 3}
---
 data/pandoc.List.lua            |  17 +++++
 doc/lua-filters.md              |  59 ++++++++++++++++
 test/lua/module/pandoc-list.lua | 120 +++++++++++++++++++++-----------
 3 files changed, 156 insertions(+), 40 deletions(-)

diff --git a/data/pandoc.List.lua b/data/pandoc.List.lua
index f5b118965..b33c30876 100644
--- a/data/pandoc.List.lua
+++ b/data/pandoc.List.lua
@@ -104,6 +104,12 @@ function List:includes (needle, init)
   return not (List.find(self, needle, init) == nil)
 end
 
+--- Insert an element into the list. Alias for `table.insert`.
+-- @param list list
+-- @param[opt] pos position at which the new element is to be inserted
+-- @param value value to insert
+List.insert = table.insert
+
 --- Returns a copy of the current list by applying the given function to
 -- all elements.
 -- @param fn function which is applied to all list items.
@@ -115,6 +121,17 @@ function List:map (fn)
   return res
 end
 
+--- Remove element from list (alias for `table.remove`)
+-- @param list list
+-- @param[opt] pos position of the element to be removed (default: #list)
+-- @return the removed element
+List.remove = table.remove
+
+--- Sort list in-place (alias for `table.sort`)
+-- @param list list
+-- @param[opt] comp comparison function; default to `<` operator.
+List.sort = table.sort
+
 -- Set metatable with __call metamethod. This allows the use of `List`
 -- as a constructor function.
 local ListMT = {
diff --git a/doc/lua-filters.md b/doc/lua-filters.md
index 3c4f1f0c8..729d12f86 100644
--- a/doc/lua-filters.md
+++ b/doc/lua-filters.md
@@ -2734,6 +2734,22 @@ methods and convenience functions.
     Returns: true if a list item is equal to the needle, false
     otherwise
 
+[`pandoc.List:insert ([pos], value)`]{#pandoc.list:insert}
+
+:   Inserts element `value` at position `pos` in list, shifting
+    elements to the next-greater indix if necessary.
+
+    This function is identical to
+    [`table.insert`](https://www.lua.org/manual/5.3/manual.html#6.6).
+
+    Parameters:
+
+    `pos`:
+    :   index of the new value; defaults to length of the list + 1
+
+    `value`:
+    :   value to insert into the list
+
 [`pandoc.List:map (fn)`]{#pandoc.list:map}
 
 :   Returns a copy of the current list by applying the given
@@ -2757,6 +2773,49 @@ methods and convenience functions.
 
     Returns: the updated input value
 
+[`pandoc.List:remove ([pos])`]{#pandoc.list:remove}
+
+:    Removes the element at position `pos`, returning the value
+     of the removed element.
+
+     This function is identical to
+     [`table.remove`](https://www.lua.org/manual/5.3/manual.html#6.6).
+
+    Parameters:
+
+    `pos`:
+    :   position of the list value that will be remove; defaults
+        to the index of the last element
+
+    Returns: the removed element
+
+[`pandoc.List:sort ([comp])`]{#pandoc.list:sort}
+
+:   Sorts list elements in a given order, in-place. If `comp` is
+    given, then it must be a function that receives two list
+    elements and returns true when the first element must come
+    before the second in the final order (so that, after the
+    sort, `i < j` implies `not comp(list[j],list[i]))`. If comp
+    is not given, then the standard Lua operator `<` is used
+    instead.
+
+    Note that the comp function must define a strict partial
+    order over the elements in the list; that is, it must be
+    asymmetric and transitive. Otherwise, no valid sort may be
+    possible.
+
+    The sort algorithm is not stable: elements considered equal
+    by the given order may have their relative positions changed
+    by the sort.
+
+    This function is identical to
+    [`table.sort`](https://www.lua.org/manual/5.3/manual.html#6.6).
+
+    Parameters:
+
+    `comp`:
+    :   Comparison function as described above.
+
 # Module pandoc.system
 
 Access to system information and functionality.
diff --git a/test/lua/module/pandoc-list.lua b/test/lua/module/pandoc-list.lua
index fb0c1c495..27790ce96 100644
--- a/test/lua/module/pandoc-list.lua
+++ b/test/lua/module/pandoc-list.lua
@@ -15,33 +15,6 @@ return {
     end)
   },
 
-  group 'new' {
-    test('make table usable as list', function ()
-      local test = List:new{1, 1, 2, 3, 5}
-      assert.are_same(
-        {1, 1, 4, 9, 25},
-        test:map(function (x) return x^2 end)
-      )
-    end),
-    test('return empty list if no argument is given', function ()
-       assert.are_same({}, List:new())
-    end),
-    test('metatable of result is pandoc.List', function ()
-      local test = List:new{5}
-      assert.are_equal(List, getmetatable(test))
-    end)
-  },
-
-  group 'includes' {
-    test('finds elements in list', function ()
-      local lst = List:new {'one', 'two', 'three'}
-      assert.is_truthy(lst:includes('one'))
-      assert.is_truthy(lst:includes('two'))
-      assert.is_truthy(lst:includes('three'))
-      assert.is_falsy(lst:includes('four'))
-    end)
-  },
-
   group 'clone' {
     test('changing the clone does not affect original', function ()
       local orig = List:new {23, 42}
@@ -54,7 +27,25 @@ return {
       local orig = List:new {23, 42}
       assert.are_equal(List, getmetatable(orig:clone()))
     end),
-                },
+  },
+
+  group 'extend' {
+    test('extends list with other list', function ()
+      local primes = List:new {2, 3, 5, 7}
+      primes:extend {11, 13, 17}
+      assert.are_same({2, 3, 5, 7, 11, 13, 17}, primes)
+    end)
+  },
+
+  group 'filter' {
+    test('keep elements for which property is truthy', function ()
+      local is_small_prime = function (x)
+        return List.includes({2, 3, 5, 7}, x)
+      end
+      local numbers = List:new {4, 7, 2, 9, 5, 11}
+      assert.are_same({7, 2, 5}, numbers:filter(is_small_prime))
+    end),
+  },
 
   group 'find' {
     test('returns element and index if found', function ()
@@ -87,11 +78,26 @@ return {
     end),
   },
 
-  group 'extend' {
-    test('extends list with other list', function ()
-      local primes = List:new {2, 3, 5, 7}
-      primes:extend {11, 13, 17}
-      assert.are_same({2, 3, 5, 7, 11, 13, 17}, primes)
+  group 'includes' {
+    test('finds elements in list', function ()
+      local lst = List:new {'one', 'two', 'three'}
+      assert.is_truthy(lst:includes('one'))
+      assert.is_truthy(lst:includes('two'))
+      assert.is_truthy(lst:includes('three'))
+      assert.is_falsy(lst:includes('four'))
+    end)
+  },
+
+  group 'insert' {
+    test('insert value at end of list.', function ()
+      local count_norsk = List {'en', 'to', 'tre'}
+      count_norsk:insert('fire')
+      assert.are_same({'en', 'to', 'tre', 'fire'}, count_norsk)
+    end),
+    test('insert value in the middle of list.', function ()
+      local count_norsk = List {'fem', 'syv'}
+      count_norsk:insert(2, 'seks')
+      assert.are_same({'fem', 'seks', 'syv'}, count_norsk)
     end)
   },
 
@@ -108,13 +114,47 @@ return {
     end)
   },
 
-  group 'filter' {
-    test('keep elements for which property is truthy', function ()
-      local is_small_prime = function (x)
-        return List.includes({2, 3, 5, 7}, x)
-      end
-      local numbers = List:new {4, 7, 2, 9, 5, 11}
-      assert.are_same({7, 2, 5}, numbers:filter(is_small_prime))
+  group 'new' {
+    test('make table usable as list', function ()
+      local test = List:new{1, 1, 2, 3, 5}
+      assert.are_same(
+        {1, 1, 4, 9, 25},
+        test:map(function (x) return x^2 end)
+      )
     end),
+    test('return empty list if no argument is given', function ()
+       assert.are_same({}, List:new())
+    end),
+    test('metatable of result is pandoc.List', function ()
+      local test = List:new{5}
+      assert.are_equal(List, getmetatable(test))
+    end)
+  },
+
+  group 'remove' {
+    test('remove value at end of list.', function ()
+      local understand = List {'jeg', 'forstår', 'ikke'}
+      local norsk_not = understand:remove()
+      assert.are_same({'jeg', 'forstår'}, understand)
+      assert.are_equal('ikke', norsk_not)
+    end),
+    test('remove value at beginning of list.', function ()
+      local count_norsk = List {'en', 'to', 'tre'}
+      count_norsk:remove(1)
+      assert.are_same({'to', 'tre'}, count_norsk)
+    end)
+  },
+
+  group 'sort' {
+    test('sort numeric list', function ()
+      local numbers = List {71, 5, -1, 42, 23, 0, 1}
+      numbers:sort()
+      assert.are_same({-1, 0, 1, 5, 23, 42, 71}, numbers)
+    end),
+    test('reverse-sort numeric', function ()
+      local numbers = List {71, 5, -1, 42, 23, 0, 1}
+      numbers:sort(function (x, y) return x > y end)
+      assert.are_same({71, 42, 23, 5, 1, 0, -1}, numbers)
+    end)
   },
 }