1
-- Copyright (c) 2009 Marcus Irven
3
-- Permission is hereby granted, free of charge, to any person
4
-- obtaining a copy of this software and associated documentation
5
-- files (the "Software"), to deal in the Software without
6
-- restriction, including without limitation the rights to use,
7
-- copy, modify, merge, publish, distribute, sublicense, and/or sell
8
-- copies of the Software, and to permit persons to whom the
9
-- Software is furnished to do so, subject to the following
12
-- The above copyright notice and this permission notice shall be
13
-- included in all copies or substantial portions of the Software.
15
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
-- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
-- OTHER DEALINGS IN THE SOFTWARE.
24
--- Underscore is a set of utility functions for dealing with
25
-- iterators, arrays, tables, and functions.
27
local Underscore = { funcs = {} }
28
Underscore.__index = Underscore
30
function Underscore.__call(_, value)
31
return Underscore:new(value)
34
function Underscore:new(value, chained)
35
return setmetatable({ _val = value, chained = chained or false }, self)
38
function Underscore.iter(list_or_iter)
39
if type(list_or_iter) == "function" then return list_or_iter end
41
return coroutine.wrap(function()
42
for i=1,#list_or_iter do
43
coroutine.yield(list_or_iter[i])
48
function Underscore.range(start_i, end_i, step)
54
local range_iter = coroutine.wrap(function()
55
for i=start_i, end_i, step do
59
return Underscore:new(range_iter)
62
--- Identity function. This function looks useless, but is used throughout Underscore as a default.
64
-- @param value any object
66
-- @usage _.identity("foo")
68
function Underscore.identity(value)
74
function Underscore:chain()
79
function Underscore:value()
85
function Underscore.funcs.each(list, func)
86
for i in Underscore.iter(list) do
92
function Underscore.funcs.map(list, func)
94
for i in Underscore.iter(list) do
95
mapped[#mapped+1] = func(i)
100
function Underscore.funcs.reduce(list, memo, func)
101
for i in Underscore.iter(list) do
107
function Underscore.funcs.detect(list, func)
108
for i in Underscore.iter(list) do
109
if func(i) then return i end
114
function Underscore.funcs.select(list, func)
116
for i in Underscore.iter(list) do
117
if func(i) then selected[#selected+1] = i end
122
function Underscore.funcs.reject(list, func)
124
for i in Underscore.iter(list) do
125
if not func(i) then selected[#selected+1] = i end
130
function Underscore.funcs.all(list, func)
131
func = func or Underscore.identity
133
-- TODO what should happen with an empty list?
134
for i in Underscore.iter(list) do
135
if not func(i) then return false end
140
function Underscore.funcs.any(list, func)
141
func = func or Underscore.identity
143
-- TODO what should happen with an empty list?
144
for i in Underscore.iter(list) do
145
if func(i) then return true end
150
function Underscore.funcs.include(list, value)
151
for i in Underscore.iter(list) do
152
if i == value then return true end
157
function Underscore.funcs.invoke(list, function_name, ...)
159
Underscore.funcs.each(list, function(i) i[function_name](i, unpack(args)) end)
163
function Underscore.funcs.pluck(list, propertyName)
164
return Underscore.funcs.map(list, function(i) return i[propertyName] end)
167
function Underscore.funcs.min(list, func)
168
func = func or Underscore.identity
170
return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(min, item)
171
if min.item == nil then
173
min.value = func(item)
175
local value = func(item)
176
if value < min.value then
185
function Underscore.funcs.max(list, func)
186
func = func or Underscore.identity
188
return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(max, item)
189
if max.item == nil then
191
max.value = func(item)
193
local value = func(item)
194
if value > max.value then
203
function Underscore.funcs.to_array(list)
205
for i in Underscore.iter(list) do
211
function Underscore.funcs.reverse(list)
213
for i in Underscore.iter(list) do
214
table.insert(reversed, 1, i)
219
function Underscore.funcs.sort(iter, comparison_func)
221
if type(iter) == "function" then
222
array = Underscore.funcs.to_array(iter)
224
table.sort(array, comparison_func)
230
function Underscore.funcs.first(array, n)
235
n = math.min(n,#array)
243
function Underscore.funcs.rest(array, index)
246
for i=index,#array do
247
rest[#rest+1] = array[i]
252
function Underscore.funcs.slice(array, start_index, length)
253
local sliced_array = {}
255
start_index = math.max(start_index, 1)
256
local end_index = math.min(start_index+length-1, #array)
257
for i=start_index, end_index do
258
sliced_array[#sliced_array+1] = array[i]
263
function Underscore.funcs.flatten(array)
266
for ele in Underscore.iter(array) do
267
if type(ele) == "table" then
268
local flattened_element = Underscore.funcs.flatten(ele)
269
Underscore.funcs.each(flattened_element, function(e) all[#all+1] = e end)
277
function Underscore.funcs.push(array, item)
278
table.insert(array, item)
282
function Underscore.funcs.pop(array)
283
return table.remove(array)
286
function Underscore.funcs.shift(array)
287
return table.remove(array, 1)
290
function Underscore.funcs.unshift(array, item)
291
table.insert(array, 1, item)
295
function Underscore.funcs.join(array, separator)
296
return table.concat(array, separator)
301
function Underscore.funcs.keys(obj)
303
for k,v in pairs(obj) do
309
function Underscore.funcs.values(obj)
311
for k,v in pairs(obj) do
312
values[#values+1] = v
317
function Underscore.funcs.extend(destination, source)
318
for k,v in pairs(source) do
324
function Underscore.funcs.is_empty(obj)
325
return next(obj) == nil
328
-- Originally based on penlight's deepcompare() -- http://luaforge.net/projects/penlight/
329
function Underscore.funcs.is_equal(o1, o2, ignore_mt)
332
if ty1 ~= ty2 then return false end
334
-- non-table types can be directly compared
335
if ty1 ~= 'table' then return o1 == o2 end
337
-- as well as tables which have the metamethod __eq
338
local mt = getmetatable(o1)
339
if not ignore_mt and mt and mt.__eq then return o1 == o2 end
341
local is_equal = Underscore.funcs.is_equal
343
for k1,v1 in pairs(o1) do
345
if v2 == nil or not is_equal(v1,v2, ignore_mt) then return false end
347
for k2,v2 in pairs(o2) do
349
if v1 == nil then return false end
356
function Underscore.funcs.compose(...)
357
local function call_funcs(funcs, ...)
359
return funcs[1](call_funcs(_.rest(funcs), ...))
367
return call_funcs(funcs, ...)
371
function Underscore.funcs.wrap(func, wrapper)
373
return wrapper(func, ...)
377
function Underscore.funcs.curry(func, argument)
379
return func(argument, ...)
383
function Underscore.functions()
384
return Underscore.keys(Underscore.funcs)
388
Underscore.methods = Underscore.functions
390
Underscore.funcs.for_each = Underscore.funcs.each
391
Underscore.funcs.collect = Underscore.funcs.map
392
Underscore.funcs.inject = Underscore.funcs.reduce
393
Underscore.funcs.foldl = Underscore.funcs.reduce
394
Underscore.funcs.filter = Underscore.funcs.select
395
Underscore.funcs.every = Underscore.funcs.all
396
Underscore.funcs.some = Underscore.funcs.any
397
Underscore.funcs.head = Underscore.funcs.first
398
Underscore.funcs.tail = Underscore.funcs.rest
400
local function wrap_functions_for_oo_support()
401
local function value_and_chained(value_or_self)
402
local chained = false
403
if getmetatable(value_or_self) == Underscore then
404
chained = value_or_self.chained
405
value_or_self = value_or_self._val
407
return value_or_self, chained
410
local function value_or_wrap(value, chained)
411
if chained then value = Underscore:new(value, true) end
415
for fn, func in pairs(Underscore.funcs) do
416
Underscore[fn] = function(obj_or_self, ...)
417
local obj, chained = value_and_chained(obj_or_self)
418
return value_or_wrap(func(obj, ...), chained)
423
wrap_functions_for_oo_support()
425
return Underscore:new()