/zoeplat

To get this branch, use:
bzr branch http://9ix.org/bzr/zoeplat

« back to all changes in this revision

Viewing changes to underscore.lua

  • Committer: Josh C
  • Date: 2013-04-23 16:15:50 UTC
  • Revision ID: josh@9ix.org-20130423161550-2769i8ut566706p3
don't require underscore

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-- Copyright (c) 2009 Marcus Irven
 
2
--  
 
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
 
10
-- conditions:
 
11
--  
 
12
-- The above copyright notice and this permission notice shall be
 
13
-- included in all copies or substantial portions of the Software.
 
14
--  
 
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.
 
23
 
 
24
--- Underscore is a set of utility functions for dealing with 
 
25
-- iterators, arrays, tables, and functions.
 
26
 
 
27
local Underscore = { funcs = {} }
 
28
Underscore.__index = Underscore
 
29
 
 
30
function Underscore.__call(_, value)
 
31
        return Underscore:new(value)
 
32
end
 
33
 
 
34
function Underscore:new(value, chained)
 
35
        return setmetatable({ _val = value, chained = chained or false }, self)
 
36
end
 
37
 
 
38
function Underscore.iter(list_or_iter)
 
39
        if type(list_or_iter) == "function" then return list_or_iter end
 
40
        
 
41
        return coroutine.wrap(function() 
 
42
                for i=1,#list_or_iter do
 
43
                        coroutine.yield(list_or_iter[i])
 
44
                end
 
45
        end)
 
46
end
 
47
 
 
48
function Underscore.range(start_i, end_i, step)
 
49
        if end_i == nil then
 
50
                end_i = start_i
 
51
                start_i = 1
 
52
        end
 
53
        step = step or 1
 
54
        local range_iter = coroutine.wrap(function() 
 
55
                for i=start_i, end_i, step do
 
56
                        coroutine.yield(i)
 
57
                end
 
58
        end)
 
59
        return Underscore:new(range_iter)
 
60
end
 
61
 
 
62
--- Identity function. This function looks useless, but is used throughout Underscore as a default.
 
63
-- @name _.identity
 
64
-- @param value any object
 
65
-- @return value
 
66
-- @usage _.identity("foo")
 
67
-- => "foo"
 
68
function Underscore.identity(value)
 
69
        return value
 
70
end
 
71
 
 
72
-- chaining
 
73
 
 
74
function Underscore:chain()
 
75
        self.chained = true
 
76
        return self
 
77
end
 
78
 
 
79
function Underscore:value()
 
80
        return self._val
 
81
end
 
82
 
 
83
-- iter
 
84
 
 
85
function Underscore.funcs.each(list, func)
 
86
        for i in Underscore.iter(list) do
 
87
                func(i)
 
88
        end
 
89
        return list
 
90
end
 
91
 
 
92
function Underscore.funcs.map(list, func)
 
93
        local mapped = {}
 
94
        for i in Underscore.iter(list) do
 
95
                mapped[#mapped+1] = func(i)
 
96
        end     
 
97
        return mapped
 
98
end
 
99
 
 
100
function Underscore.funcs.reduce(list, memo, func)      
 
101
        for i in Underscore.iter(list) do
 
102
                memo = func(memo, i)
 
103
        end     
 
104
        return memo
 
105
end
 
106
 
 
107
function Underscore.funcs.detect(list, func)
 
108
        for i in Underscore.iter(list) do
 
109
                if func(i) then return i end
 
110
        end     
 
111
        return nil      
 
112
end
 
113
 
 
114
function Underscore.funcs.select(list, func)
 
115
        local selected = {}
 
116
        for i in Underscore.iter(list) do
 
117
                if func(i) then selected[#selected+1] = i end
 
118
        end
 
119
        return selected
 
120
end
 
121
 
 
122
function Underscore.funcs.reject(list, func)
 
123
        local selected = {}
 
124
        for i in Underscore.iter(list) do
 
125
                if not func(i) then selected[#selected+1] = i end
 
126
        end
 
127
        return selected
 
128
end
 
129
 
 
130
function Underscore.funcs.all(list, func)
 
131
        func = func or Underscore.identity
 
132
        
 
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
 
136
        end
 
137
        return true
 
138
end
 
139
 
 
140
function Underscore.funcs.any(list, func)
 
141
        func = func or Underscore.identity
 
142
 
 
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
 
146
        end     
 
147
        return false
 
148
end
 
149
 
 
150
function Underscore.funcs.include(list, value)
 
151
        for i in Underscore.iter(list) do
 
152
                if i == value then return true end
 
153
        end     
 
154
        return false
 
155
end
 
156
 
 
157
function Underscore.funcs.invoke(list, function_name, ...)
 
158
        local args = {...}
 
159
        Underscore.funcs.each(list, function(i) i[function_name](i, unpack(args)) end)
 
160
        return list
 
161
end
 
162
 
 
163
function Underscore.funcs.pluck(list, propertyName)
 
164
        return Underscore.funcs.map(list, function(i) return i[propertyName] end)
 
165
end
 
166
 
 
167
function Underscore.funcs.min(list, func)
 
168
        func = func or Underscore.identity
 
169
        
 
170
        return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(min, item) 
 
171
                if min.item == nil then
 
172
                        min.item = item
 
173
                        min.value = func(item)
 
174
                else
 
175
                        local value = func(item)
 
176
                        if value < min.value then
 
177
                                min.item = item
 
178
                                min.value = value
 
179
                        end
 
180
                end
 
181
                return min
 
182
        end).item
 
183
end
 
184
 
 
185
function Underscore.funcs.max(list, func)
 
186
        func = func or Underscore.identity
 
187
        
 
188
        return Underscore.funcs.reduce(list, { item = nil, value = nil }, function(max, item) 
 
189
                if max.item == nil then
 
190
                        max.item = item
 
191
                        max.value = func(item)
 
192
                else
 
193
                        local value = func(item)
 
194
                        if value > max.value then
 
195
                                max.item = item
 
196
                                max.value = value
 
197
                        end
 
198
                end
 
199
                return max
 
200
        end).item
 
201
end
 
202
 
 
203
function Underscore.funcs.to_array(list)
 
204
        local array = {}
 
205
        for i in Underscore.iter(list) do
 
206
                array[#array+1] = i
 
207
        end     
 
208
        return array
 
209
end
 
210
 
 
211
function Underscore.funcs.reverse(list)
 
212
        local reversed = {}
 
213
        for i in Underscore.iter(list) do
 
214
                table.insert(reversed, 1, i)
 
215
        end     
 
216
        return reversed
 
217
end
 
218
 
 
219
function Underscore.funcs.sort(iter, comparison_func)
 
220
        local array = iter
 
221
        if type(iter) == "function" then
 
222
                array = Underscore.funcs.to_array(iter)
 
223
        end
 
224
        table.sort(array, comparison_func)
 
225
        return array
 
226
end
 
227
 
 
228
-- arrays
 
229
 
 
230
function Underscore.funcs.first(array, n)
 
231
        if n == nil then
 
232
                return array[1]
 
233
        else
 
234
                local first = {}
 
235
                n = math.min(n,#array)
 
236
                for i=1,n do
 
237
                        first[i] = array[i]                     
 
238
                end
 
239
                return first
 
240
        end
 
241
end
 
242
 
 
243
function Underscore.funcs.rest(array, index)
 
244
        index = index or 2
 
245
        local rest = {}
 
246
        for i=index,#array do
 
247
                rest[#rest+1] = array[i]
 
248
        end
 
249
        return rest
 
250
end
 
251
 
 
252
function Underscore.funcs.slice(array, start_index, length)
 
253
        local sliced_array = {}
 
254
        
 
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]
 
259
        end
 
260
        return sliced_array
 
261
end
 
262
 
 
263
function Underscore.funcs.flatten(array)
 
264
        local all = {}
 
265
        
 
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)
 
270
                else
 
271
                        all[#all+1] = ele
 
272
                end
 
273
        end
 
274
        return all
 
275
end
 
276
 
 
277
function Underscore.funcs.push(array, item)
 
278
        table.insert(array, item)
 
279
        return array
 
280
end
 
281
 
 
282
function Underscore.funcs.pop(array)
 
283
        return table.remove(array)
 
284
end
 
285
 
 
286
function Underscore.funcs.shift(array)
 
287
        return table.remove(array, 1)
 
288
end
 
289
 
 
290
function Underscore.funcs.unshift(array, item)
 
291
        table.insert(array, 1, item)
 
292
        return array
 
293
end
 
294
 
 
295
function Underscore.funcs.join(array, separator)
 
296
        return table.concat(array, separator)
 
297
end
 
298
 
 
299
-- objects
 
300
 
 
301
function Underscore.funcs.keys(obj)
 
302
        local keys = {}
 
303
        for k,v in pairs(obj) do
 
304
                keys[#keys+1] = k
 
305
        end
 
306
        return keys
 
307
end
 
308
 
 
309
function Underscore.funcs.values(obj)
 
310
        local values = {}
 
311
        for k,v in pairs(obj) do
 
312
                values[#values+1] = v
 
313
        end
 
314
        return values
 
315
end
 
316
 
 
317
function Underscore.funcs.extend(destination, source)
 
318
        for k,v in pairs(source) do
 
319
                destination[k] = v
 
320
        end     
 
321
        return destination
 
322
end
 
323
 
 
324
function Underscore.funcs.is_empty(obj)
 
325
        return next(obj) == nil
 
326
end
 
327
 
 
328
-- Originally based on penlight's deepcompare() -- http://luaforge.net/projects/penlight/
 
329
function Underscore.funcs.is_equal(o1, o2, ignore_mt)
 
330
        local ty1 = type(o1)
 
331
        local ty2 = type(o2)
 
332
        if ty1 ~= ty2 then return false end
 
333
        
 
334
        -- non-table types can be directly compared
 
335
        if ty1 ~= 'table' then return o1 == o2 end
 
336
        
 
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
 
340
        
 
341
        local is_equal = Underscore.funcs.is_equal
 
342
        
 
343
        for k1,v1 in pairs(o1) do
 
344
                local v2 = o2[k1]
 
345
                if v2 == nil or not is_equal(v1,v2, ignore_mt) then return false end
 
346
        end
 
347
        for k2,v2 in pairs(o2) do
 
348
                local v1 = o1[k2]
 
349
                if v1 == nil then return false end
 
350
        end
 
351
        return true
 
352
end
 
353
 
 
354
-- functions
 
355
 
 
356
function Underscore.funcs.compose(...)
 
357
        local function call_funcs(funcs, ...)
 
358
                if #funcs > 1 then
 
359
                        return funcs[1](call_funcs(_.rest(funcs), ...))
 
360
                else
 
361
                        return funcs[1](...)
 
362
                end
 
363
        end
 
364
        
 
365
        local funcs = {...}
 
366
        return function(...)
 
367
                return call_funcs(funcs, ...)
 
368
        end
 
369
end
 
370
 
 
371
function Underscore.funcs.wrap(func, wrapper)
 
372
        return function(...)
 
373
                return wrapper(func, ...)
 
374
        end
 
375
end
 
376
 
 
377
function Underscore.funcs.curry(func, argument)
 
378
        return function(...)
 
379
                return func(argument, ...)
 
380
        end
 
381
end
 
382
 
 
383
function Underscore.functions() 
 
384
        return Underscore.keys(Underscore.funcs)
 
385
end
 
386
 
 
387
-- add aliases
 
388
Underscore.methods = Underscore.functions
 
389
 
 
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
 
399
 
 
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 
 
406
                end
 
407
                return value_or_self, chained
 
408
        end
 
409
 
 
410
        local function value_or_wrap(value, chained)
 
411
                if chained then value = Underscore:new(value, true) end
 
412
                return value
 
413
        end
 
414
 
 
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)           
 
419
                end      
 
420
        end
 
421
end
 
422
 
 
423
wrap_functions_for_oo_support()
 
424
 
 
425
return Underscore:new()