2
--- Underscore is a set of utility functions for dealing with
4
local Underscore = { funcs = {} }
5
Underscore.__index = Underscore
7
function Underscore.__call(_, value)
8
return Underscore:new(value)
11
function Underscore:new(value, chained)
12
return setmetatable({ _val = value, chained = chained or false }, self)
15
function Underscore.iter(list_or_iter)
16
if type(list_or_iter) == "function" then return list_or_iter end
18
return coroutine.wrap(function()
19
for i=1,#list_or_iter do
20
coroutine.yield(list_or_iter[i])
25
function Underscore.range(start_i, end_i, step)
31
local range_iter = coroutine.wrap(function()
32
for i=start_i, end_i, step do
36
return Underscore:new(range_iter)
39
--- Identity function. This function looks useless, but is used throughout Underscore as a default.
40
function Underscore.identity(value)
45
function Underscore:chain()
50
function Underscore:value()
55
function Underscore.funcs.each(list, func)
56
for i in Underscore.iter(list) do
62
function Underscore.funcs.map(list, func)
64
for i in Underscore.iter(list) do
65
mapped[#mapped+1] = func(i)
70
function Underscore.funcs.reduce(list, memo, func)
71
for i in Underscore.iter(list) do
77
function Underscore.funcs.detect(list, func)
78
for i in Underscore.iter(list) do
79
if func(i) then return i end
84
function Underscore.funcs.select(list, func)
86
for i in Underscore.iter(list) do
87
if func(i) then selected[#selected+1] = i end
92
function Underscore.funcs.reject(list, func)
94
for i in Underscore.iter(list) do
95
if not func(i) then selected[#selected+1] = i end
100
function Underscore.funcs.all(list, func)
101
func = func or Underscore.identity
103
-- TODO what should happen with an empty list?
104
for i in Underscore.iter(list) do
105
if not func(i) then return false end
110
function Underscore.funcs.any(list, func)
111
func = func or Underscore.identity
113
-- TODO what should happen with an empty list?
114
for i in Underscore.iter(list) do
115
if func(i) then return true end
120
function Underscore.funcs.include(list, value)
121
for i in Underscore.iter(list) do
122
if i == value then return true end
127
function Underscore.funcs.invoke(list, function_name, ...)
129
Underscore.funcs.each(list, function(i) i[function_name](i, unpack(args)) end)
133
function Underscore.funcs.pluck(list, propertyName)
134
return Underscore.funcs.map(list, function(i) return i[propertyName] end)
137
function Underscore.funcs.min(list, func)
138
func = func or Underscore.identity
140
return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(min, item)
141
if min.item == nil then
143
min.value = func(item)
145
local value = func(item)
146
if value < min.value then
155
function Underscore.funcs.max(list, func)
156
func = func or Underscore.identity
158
return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(max, item)
159
if max.item == nil then
161
max.value = func(item)
163
local value = func(item)
164
if value > max.value then
173
function Underscore.funcs.to_array(list)
175
for i in Underscore.iter(list) do
181
function Underscore.funcs.reverse(list)
183
for i in Underscore.iter(list) do
184
table.insert(reversed, 1, i)
189
function Underscore.funcs.sort(iter, comparison_func)
191
if type(iter) == "function" then
192
array = Underscore.funcs.to_array(iter)
194
table.sort(array, comparison_func)
199
function Underscore.funcs.first(array, n)
204
n = math.min(n,#array)
212
function Underscore.funcs.rest(array, index)
215
for i=index,#array do
216
rest[#rest+1] = array[i]
221
function Underscore.funcs.slice(array, start_index, length)
222
local sliced_array = {}
224
start_index = math.max(start_index, 1)
225
local end_index = math.min(start_index+length-1, #array)
226
for i=start_index, end_index do
227
sliced_array[#sliced_array+1] = array[i]
232
function Underscore.funcs.flatten(array)
235
for ele in Underscore.iter(array) do
236
if type(ele) == "table" then
237
local flattened_element = Underscore.funcs.flatten(ele)
238
Underscore.funcs.each(flattened_element, function(e) all[#all+1] = e end)
246
function Underscore.funcs.push(array, item)
247
table.insert(array, item)
251
function Underscore.funcs.pop(array)
252
return table.remove(array)
255
function Underscore.funcs.shift(array)
256
return table.remove(array, 1)
259
function Underscore.funcs.unshift(array, item)
260
table.insert(array, 1, item)
264
function Underscore.funcs.join(array, separator)
265
return table.concat(array, separator)
269
function Underscore.funcs.keys(obj)
271
for k,v in pairs(obj) do
277
function Underscore.funcs.values(obj)
279
for k,v in pairs(obj) do
280
values[#values+1] = v
285
function Underscore.funcs.extend(destination, source)
286
for k,v in pairs(source) do
292
function Underscore.funcs.is_empty(obj)
293
return next(obj) == nil
296
function Underscore.funcs.is_equal(o1, o2, ignore_mt)
299
if ty1 ~= ty2 then return false end
301
-- non-table types can be directly compared
302
if ty1 ~= 'table' then return o1 == o2 end
304
-- as well as tables which have the metamethod __eq
305
local mt = getmetatable(o1)
306
if not ignore_mt and mt and mt.__eq then return o1 == o2 end
308
local is_equal = Underscore.funcs.is_equal
310
for k1,v1 in pairs(o1) do
312
if v2 == nil or not is_equal(v1,v2, ignore_mt) then return false end
314
for k2,v2 in pairs(o2) do
316
if v1 == nil then return false end
322
function Underscore.funcs.compose(...)
323
local function call_funcs(funcs, ...)
325
return funcs[1](call_funcs(_.rest(funcs), ...))
333
return call_funcs(funcs, ...)
337
function Underscore.funcs.wrap(func, wrapper)
339
return wrapper(func, ...)
343
function Underscore.funcs.curry(func, argument)
345
return func(argument, ...)
349
function Underscore.functions()
350
return Underscore.keys(Underscore.funcs)
353
Underscore.methods = Underscore.functions
355
Underscore.funcs.for_each = Underscore.funcs.each
356
Underscore.funcs.collect = Underscore.funcs.map
357
Underscore.funcs.inject = Underscore.funcs.reduce
358
Underscore.funcs.foldl = Underscore.funcs.reduce
359
Underscore.funcs.filter = Underscore.funcs.select
360
Underscore.funcs.every = Underscore.funcs.all
361
Underscore.funcs.some = Underscore.funcs.any
362
Underscore.funcs.head = Underscore.funcs.first
363
Underscore.funcs.tail = Underscore.funcs.rest
365
local function wrap_functions_for_oo_support()
366
local function value_and_chained(value_or_self)
367
local chained = false
368
if getmetatable(value_or_self) == Underscore then
369
chained = value_or_self.chained
370
value_or_self = value_or_self._val
372
return value_or_self, chained
375
local function value_or_wrap(value, chained)
376
if chained then value = Underscore:new(value, true) end
380
for fn, func in pairs(Underscore.funcs) do
381
Underscore[fn] = function(obj_or_self, ...)
382
local obj, chained = value_and_chained(obj_or_self)
383
return value_or_wrap(func(obj, ...), chained)
388
wrap_functions_for_oo_support()
390
return Underscore:new()