92 Commits

Author SHA1 Message Date
rxi
0e4256ef0d Version 1.5.0 2014-12-21 12:25:38 +00:00
rxi
bcad07fc5f Added explanation of calling lume() to the README 2014-12-13 01:00:47 +00:00
rxi
8f1267f967 Added lume.first() and .last(), updated README.md and tests 2014-12-12 21:33:41 +00:00
rxi
de37bd6d65 Added lume.keys() and tests, updated docs 2014-12-12 20:40:25 +00:00
rxi
ac920c8f5e Fixed lume.invert test name 2014-12-12 20:28:48 +00:00
rxi
5d258d4fd0 Made calling lume table an alias to calling lume.chain()
Updated tests
2014-12-12 20:25:54 +00:00
rxi
df26e7939d Added tests for funcitons which now use iteratee() 2014-12-12 20:22:29 +00:00
rxi
16e370cdf0 Changed all applicable functions to use iteratee() internally 2014-12-12 19:45:22 +00:00
rxi
0b991d7ea5 Added identity() and iteratee() local funcs 2014-12-12 18:56:13 +00:00
rxi
f6174a4b68 Version 1.4.1 2014-07-17 22:02:39 +01:00
rxi
d99bf9549a Removed unnecessary pack & unpack from lume.array() 2014-06-16 21:07:40 +01:00
rxi
0cc52cd24e Version 1.4.0 2014-05-18 08:46:28 +01:00
rxi
0a8258d608 Made some minor formatting changes 2014-05-07 08:21:36 +01:00
rxi
95c3f8960d Merge pull request #3 from icrawler/Dev2
Stripped all trailing whitespace, changed ease function, and pre-computed po2
2014-05-07 08:18:22 +01:00
Phoenix Enero
6b73aaa8ad Pre-computed powers of two in lume.rgba 2014-05-07 12:11:29 +08:00
Phoenix Enero
1a82d308af Changed ease function from cosine to cubic 2014-05-07 12:11:26 +08:00
Phoenix Enero
17b58ec63d Stripped all trailing whitespace 2014-05-07 12:11:15 +08:00
rxi
5ab25046a2 Renamed local memoize_nilkey -> memoize_nil 2014-05-01 21:27:57 +01:00
rxi
3f61d823ae Changed position of lume.time() function 2014-05-01 19:23:44 +01:00
rxi
0716caf6a1 Added lume.memoize(), updated README.md and tests 2014-05-01 18:58:29 +01:00
rxi
6a160a3afe Fixed iscallable() to handle the lack of metatable properly 2014-04-28 12:52:29 +01:00
rxi
fff0d780bb Updated README.md and tests for lume.combine() 2014-04-28 12:48:44 +01:00
rxi
283f7ee787 Changed lume.combine() to ignore nil args 2014-04-28 12:46:58 +01:00
rxi
076ca7972c Version 1.3.1 2014-04-23 20:11:00 +01:00
rxi
c2311f9821 Added missing tostring() call on args in lume.trace() 2014-04-19 09:06:33 +01:00
rxi
beced85d6b Updated test for lume.trace() 2014-04-18 19:16:33 +01:00
rxi
8a2765a41b Fixed lume.trace() to handle and print nil argument 2014-04-18 19:15:13 +01:00
rxi
09847bd266 Version 1.3.0 2014-04-17 20:52:54 +01:00
rxi
841cee30e1 Added another test for lume.slice() 2014-04-17 12:30:19 +01:00
rxi
cd3c0a1eea Made callable tables be accepted as functions 2014-04-17 12:13:50 +01:00
rxi
3190d65130 Removed use of math.min and math.max in lume.slice() 2014-04-17 12:07:59 +01:00
rxi
c7471b32fb Changed lume.uuid() to use local math_random() 2014-04-05 23:08:03 +01:00
rxi
bb56f1ce3a Added missing comment for lume.uuid func in test_lua.lua 2014-04-05 16:37:05 +01:00
rxi
098754056a Added tests for lume.chain() 2014-04-05 16:35:53 +01:00
rxi
330779fb0f Added lume.chain(), added section to README.md 2014-04-05 16:30:10 +01:00
rxi
acdb58a447 Added lume.uuid(), tests and README.md section 2014-04-05 14:41:31 +01:00
rxi
88b428cb4d Added lume.count() to README.md 2014-04-05 12:21:36 +01:00
rxi
fdf01937e2 Added lume.count() function and tests 2014-04-05 12:11:41 +01:00
rxi
3a4ce4fe3b Added type check in lume.fn() and additional tests 2014-04-04 18:38:36 +01:00
rxi
1a087efe99 Added lume.match() to README.md 2014-04-04 18:21:21 +01:00
rxi
9a82d6318e Added tests for lume.match() 2014-04-04 18:09:23 +01:00
rxi
30991649f8 Added lume.match() 2014-04-04 18:08:39 +01:00
rxi
dbd93b3861 Moved position of lume.combine() in readme and other files 2014-04-03 20:17:31 +01:00
rxi
8311519e3f Added test for lume.combine() to test_lume.lua 2014-04-03 20:10:08 +01:00
rxi
82c697b08e Added lume.combine() to README.md 2014-04-03 20:09:56 +01:00
rxi
5db6be936a Added lume.combine() function 2014-04-03 20:09:40 +01:00
rxi
335b928cae Removed function in lume.slice()
Moved the function index() from lume.slice()'s body to local var
absindex, this improves the performance of lume.slice() on luajit
significantly and improves it to a lesser extent on non-jit lua.
2014-04-01 20:16:39 +01:00
rxi
94977b4f7e Updated test for lume.trace() 2014-03-30 18:38:45 +01:00
rxi
e6246834b7 Changed lume.trace() to round numbers to 2 decimal places 2014-03-30 18:31:14 +01:00
rxi
9bf2d24ee2 table.unpack() is now used if unpack() is not available. 2014-03-29 10:02:48 +00:00
rxi
89931c1ad8 Version 1.2.1 2014-03-27 17:57:19 +00:00
rxi
9ff4637201 Moved shared code of lume.split/trim() to local func 2014-03-19 21:05:34 +00:00
rxi
617729e261 Added more tests for lume.trim() 2014-03-19 20:18:32 +00:00
rxi
ad64d7af05 Fixed use of pattern special chars in lume.trim()
Using lua pattern special chars in the `chars` argument of lume.trim()
are now handled correctly and treated like any other character.
2014-03-19 20:14:48 +00:00
rxi
d69f419f5a Improved lume.split()'s README.md section for clarity 2014-03-19 19:49:50 +00:00
rxi
ad34f6ce33 Updated lume.split() in README.md 2014-03-19 13:02:55 +00:00
rxi
5882ca1303 Fixed minor error in lume.set()'s README.md example 2014-03-19 12:54:14 +00:00
rxi
828d23e6f6 Added new tests and updated some others for lume.split() 2014-03-19 12:53:01 +00:00
rxi
13a8edb2e7 Changed lume.split() to mimic python's str.split()
The lume.split() function now mimics python's str.split() function,
though it does not support the third argument (maxsplit)
2014-03-19 12:51:35 +00:00
rxi
425a52b99f Fixed spelling mistake in README.md 2014-03-18 18:01:12 +00:00
rxi
a234e36975 Version 1.2.0 2014-03-18 12:12:38 +00:00
rxi
4ba373c2d1 Localised global funcs/variables
Set most of the global funcs / variables which are used to local
variables. This offers better performance in lua 5.1 and 5.2, and seems
to make no difference in luaJIT.
2014-03-18 12:07:53 +00:00
rxi
c537d00a47 Added type tests for lume.random() 2014-03-18 12:04:36 +00:00
rxi
2c3be23a2b Added test for lume.lambda() caching 2014-03-15 21:58:35 +00:00
rxi
be40cd6276 Added caching to lume.lambda() 2014-03-15 21:58:26 +00:00
rxi
c971cee753 Added more tests for lume.slice() 2014-03-13 21:53:07 +00:00
rxi
151b57adc6 Fixed behaviour for negative i argument on lume.slice()
A negative value for the `i` argument of lume.slice() is now handled
properly (used as an index from the end of the string) to mimicing the
behaviour of lua's string.sub()
2014-03-13 21:32:22 +00:00
rxi
cbafc49e8a Added "unsupported type" error to lume.serialize()
The unsupported type error is raised if an unsupported type is
encountered in the table we are trying to serialize.
2014-03-12 21:16:32 +00:00
rxi
fcb1fa90d4 Reworded description of lume.weightedchoice() in README 2014-03-12 13:16:36 +00:00
rxi
2a6d1ea9a8 Added some tests for lume.weightedchoice() 2014-03-12 13:12:55 +00:00
rxi
2d7fa98155 Added lume.weightedchoice() 2014-03-12 13:12:45 +00:00
rxi
af4c919be4 Added tests for lume.lambda() 2014-03-09 00:11:06 +00:00
rxi
abdfd78354 Added tester.test.error() to tester.lua script 2014-03-08 23:00:47 +00:00
rxi
fbb5cbac1a Replaced table concat with str concat in lume.lambda()
This offers an improvement in performance for the function on Lua5.1,
5.2 and JIT 2.0.2
2014-03-08 22:19:55 +00:00
rxi
f8cbfc0bb0 Added lume.lambda() to README.md 2014-03-08 21:18:05 +00:00
rxi
cf031025fd Added lume.lambda() 2014-03-08 21:08:56 +00:00
rxi
4db5a6b683 Added another test for lume.reduce() 2014-03-08 16:19:31 +00:00
rxi
a4fe134985 Version 1.1.2 2014-03-08 16:10:16 +00:00
rxi
cbff46bdfb Fixed empty-table test for lume.invert() 2014-03-08 16:08:58 +00:00
rxi
5e15a57f37 Moved lume.invert()'s test func to match order in lume.lua 2014-03-08 15:47:28 +00:00
rxi
2f388bc2c7 Moved lume.invert() to be near lume.clone()
This makes the series of python-like functions (map, all, any, reduce,
set, filter) consecutive in the source and readme, which seems like a
more logical order.
2014-03-08 15:23:13 +00:00
rxi
8f4d1b158f Added empty-table test for lume.set() 2014-03-08 14:48:04 +00:00
rxi
1bbb795a89 Changed lume.set() to make use of lume.invert() 2014-03-08 14:47:27 +00:00
rxi
2c71079ef3 Added tests for lume.invert() 2014-03-08 14:45:18 +00:00
rxi
8a76fd7595 Added lume.invert() 2014-03-08 14:45:08 +00:00
rxi
b4bea5f4e0 Added new tests for lume.reduce() 2014-03-06 19:41:14 +00:00
rxi
51189d190d Made first argument in lume.reduce() optional 2014-03-06 19:33:13 +00:00
rxi
e6d47627cd Increased version to 1.1.1 2014-03-05 21:52:28 +00:00
rxi
05828bd840 Fixed module's table not updating in lume.hotswap()
Changed the returned values from pcall() in lume.hotswap() to be handled
correctly -- the second return value being the wrapped-function's first
return value. This bug would effect hotswapped modules which used the
modern approach of returning a table rather than creating globals.
2014-03-05 19:11:14 +00:00
rxi
83a051aadb Improved lume.clamp()'s performance on non-JIT
Removed use of the functions math.max() and math.min() and replaced them
with and/ors:

[5000000 calls, Lua5.2]
old func: 1.37 seconds
new func: 0.53 seconds
2014-03-05 12:44:19 +00:00
rxi
2699094218 Added better tests for lume.distance() 2014-03-05 12:22:05 +00:00
rxi
a08436445d Improved lume.distance()'s performance on non-JIT
Replaced use of the exponent operator with multiplications, yielding a
performance increase on non-JIT Lua:

[5000000 calls, Lua 5.1]
old func: 2.03 seconds
new func: 1.17 seconds

[5000000 calls, Lua 5.2]
old func: 1.60 seconds
new func: 0.89 seconds

[2000000000 calls, LuaJIT 2.0.2]
old func: 0.89 seconds
new func: 0.89 seconds
2014-03-05 12:18:49 +00:00
4 changed files with 561 additions and 70 deletions

118
README.md
View File

@@ -38,7 +38,7 @@ lume.lerp(100, 200, .5) -- Returns 150
```
### lume.smooth(a, b, amount)
Similar to `lume.lerp()` but uses cosine interpolation instead of linear
Similar to `lume.lerp()` but uses cubic interpolation instead of linear
interpolation.
### lume.pingpong(x)
@@ -64,6 +64,16 @@ raised.
lume.randomchoice({true, false}) -- Returns either true or false
```
### lume.weightedchoice(t)
Takes the argument table `t` where the keys are the possible choices and the
value is the choice's weight. A weight should be 0 or above, the larger the
number the higher the probability of that choice being picked. If the table is
empty, a weight is below zero or all the weights are 0 then an error is raised.
```lua
lume.weightedchoice({ ["cat"] = 10, ["dog"] = 5, ["frog"] = 0 })
-- Returns either "cat" or "dog" with "cat" being twice as likely to be chosen.
```
### lume.shuffle(t)
Shuffles the values of array `t` in place, returns the array.
@@ -105,12 +115,14 @@ supplied it is called on each value, true is returned if any of the calls to
lume.any({1, 2, 1}, function(x) return x == 1 end) -- Returns true
```
### lume.reduce(t, fn, first)
### lume.reduce(t, fn [, first])
Applies `fn` on two arguments cumulative to the items of the array `t`, from
left to right, so as to reduce the array to a single value. The accumulator is
intialised to the `first` value.
left to right, so as to reduce the array to a single value. If a `first` value
is specified the accumulator is initialised to this, otherwise the first value
in the array is used. If the array is empty and no `first` value is specified
an error is raised,
```lua
lume.reduce({1, 2, 3}, function(a, b) return a + b end, 0) -- Returns 6
lume.reduce({1, 2, 3}, function(a, b) return a + b end) -- Returns 6
```
### lume.set(t [, retainkeys])
@@ -118,7 +130,7 @@ Returns a copy of the `t` table with all the duplicate values removed. If
`retainkeys` is true the table is not treated as an array and retains its
original keys.
```lua
lume.set({2, 1, 2, "cat", "cat"}) -- Returns {1, 2, cat}
lume.set({2, 1, 2, "cat", "cat"}) -- Returns {1, 2, "cat"}
```
### lume.filter(t, fn [, retainkeys])
@@ -144,6 +156,21 @@ exist in the table.
lume.find({"a", "b", "c"}, "b") -- Returns 2
```
### lume.match(t, fn)
Returns the value and key of the value in table `t` which returns true when
`fn` is called on it. Returns `nil` if no such value exists.
```lua
lume.match({1, 5, 8, 7}, function(x) return x % 2 == 0 end) -- Returns 8, 3
```
### lume.count(t [, fn])
Counts the number of values in the table `t`. If a `fn` function is supplied it
is called on each value, the number of times it returns true is counted.
```lua
lume.count({a = 2, b = 3, c = 4, d = 5}) -- Returns 4
lume.count({1, 2, 4, 6}, function(x) return x % 2 == 0 end) -- Returns 3
```
### lume.slice(t [, i [, j]])
Mimics the behaviour of Lua's `string.sub`, but operates on an array rather
than a string. Creates and returns a new array of the given slice.
@@ -151,6 +178,30 @@ than a string. Creates and returns a new array of the given slice.
lume.slice({"a", "b", "c", "d", "e"}, 2, 4) -- Returns {"b", "c", "d"}
```
### lume.first(t [, n])
Returns the first element of an array or nil if the array is empty. If `n` is
specificed an array of the first `n` elements is returned.
```lua
lume.first({"a", "b", "c"}) -- Returns "a"
```
### lume.last(t [, n])
Returns the last element of an array or nil if the array is empty. If `n` is
specificed an array of the last `n` elements is returned.
```lua
lume.last({"a", "b", "c"}) -- Returns "c"
```
### lume.invert(t)
Returns a copy of the table where the keys have become the values and the
values the keys.
```lua
lume.invert({a = "x", b = "y"}) -- returns {x = "a", y = "b"}
```
### lume.keys(t)
Returns an array containing each key of the table.
### lume.clone(t)
Returns a shallow copy of the table `t`.
@@ -174,6 +225,25 @@ f() -- Prints "Hello"
f() -- Does nothing
```
### lume.memoize(fn)
Returns a wrapper function to `fn` where the results for any given set of
arguments are cached. `lume.memoize()` is useful when used on functions with
slow-running computations.
```lua
fib = lume.memoize(function(n) return n < 2 and n or fib(n-1) + fib(n-2) end)
```
### lume.combine(...)
Creates a wrapper function which calls each supplied argument in the order they
were passed to `lume.combine()`; nil arguments are ignored. The wrapper
function passes its own arguments to each of its wrapped functions when it is
called.
```lua
local f = lume.combine(function(a, b) print(a + b) end,
function(a, b) print(a * b) end)
f(3, 4) -- Prints "7" then "12" on a new line
```
### lume.time(fn, ...)
Inserts the arguments into function `fn` and calls it. Returns the time in
seconds the function `fn` took to execute followed by `fn`'s returned values.
@@ -181,6 +251,15 @@ seconds the function `fn` took to execute followed by `fn`'s returned values.
lume.time(function(x) return x end, "hello") -- Returns 0, "hello"
```
### lume.lambda(str)
Takes a string lambda and returns a function. `str` should be a list of
comma-separated parameters, followed by `->`, followed by the expression which
will be evaluated and returned.
```lua
local f = lume.lambda "x,y -> 2*x+y"
f(10, 5) -- Returns 25
```
### lume.serialize(x)
Serializes the argument `x` into a string which can be loaded again using
`lume.deserialize()`. Only booleans, numbers, tables and strings can be
@@ -199,11 +278,12 @@ lume.deserialize("{1, 2, 3}") -- Returns {1, 2, 3}
```
### lume.split(str [, sep])
Splits the string `str` into words and returns a table of the sub strings. If
`sep` is provided the string will be split at any of the characters in `sep`
instead of on whitespace.
Returns an array of the words in the string `str`. If `sep` is provided it is
used as the delimiter, consecutive delimiters are not grouped together and will
delimit empty strings.
```lua
lume.split("One two three") -- Returns {"One", "two", "three"}
lume.split("a,b,,c", ",") -- Returns {"a", "b", "", "c"}
```
### lume.trim(str [, chars])
@@ -237,6 +317,10 @@ Executes the lua code inside `str`.
lume.dostring("print('Hello!')") -- Prints "Hello!"
```
### lume.uuid()
Generates a random UUID string; version 4 as specified in
[RFC 4122](http://www.ietf.org/rfc/rfc4122.txt).
### lume.hotswap(modname)
Reloads an already loaded module in place, allowing you to immediately see the
effects of code changes without having to restart the program. `modname` should
@@ -256,6 +340,22 @@ arguments to [LÖVE](http://love2d.org)'s setColor() function.
lume.rgba(0xFF304050) -- Returns 48, 64, 80, 255
```
### lume.chain(value)
Returns a wrapped object which allows chaining of lume functions. The function
result() should be called at the end of the chain to return the resulting
value.
```lua
lume.chain({1, 2, 3, 4})
:filter(function(x) return x % 2 == 0 end)
:map(function(x) return -x end)
:result() -- Returns { -2, -4 }
```
The table returned by the `lume` module, when called, acts in the same manner
as calling `lume.chain()`.
```lua
lume({1, 2, 3}):each(print) -- Prints 1, 2 then 3 on separate lines
```
## License

290
lume.lua
View File

@@ -7,17 +7,62 @@
-- under the terms of the MIT license. See LICENSE for details.
--
local lume = { _version = "1.1.0" }
local lume = { _version = "1.5.0" }
local pairs, ipairs = pairs, ipairs
local type, assert, unpack = type, assert, unpack or table.unpack
local tostring, tonumber = tostring, tonumber
local math_floor = math.floor
local math_ceil = math.ceil
local math_random = math.random
local math_cos = math.cos
local math_atan2 = math.atan2
local math_sqrt = math.sqrt
local math_abs = math.abs
local math_pi = math.pi
local patternescape = function(str)
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
end
local absindex = function(len, i)
return i < 0 and (len + i + 1) or i
end
local iscallable = function(x)
if type(x) == "function" then return true end
local mt = getmetatable(x)
return mt and mt.__call ~= nil
end
local identity = function(x)
return x
end
local iteratee = function(x)
if x == nil then return identity end
if iscallable(x) then return x end
if type(x) == "table" then
return function(z)
for k, v in pairs(x) do
if z[k] ~= v then return false end
end
return true
end
end
return function(z) return z[x] end
end
function lume.clamp(x, min, max)
return math.max(math.min(x, max), min)
return x < min and min or (x > max and max or x)
end
function lume.round(x, increment)
if increment then return lume.round(x / increment) * increment end
return x > 0 and math.floor(x + .5) or math.ceil(x - .5)
return x > 0 and math_floor(x + .5) or math_ceil(x - .5)
end
@@ -32,42 +77,60 @@ end
function lume.smooth(a, b, amount)
local m = (1 - math.cos(lume.clamp(amount, 0, 1) * math.pi)) / 2
local t = lume.clamp(amount, 0, 1)
local m = t * t * (3 - 2 * t)
return a + (b - a) * m
end
function lume.pingpong(x)
return 1 - math.abs(1 - x % 2)
return 1 - math_abs(1 - x % 2)
end
function lume.distance(x1, y1, x2, y2, squared)
local s = (x1 - x2) ^ 2 + (y1 - y2) ^ 2
return squared and s or math.sqrt(s)
local dx = x1 - x2
local dy = y1 - y2
local s = dx * dx + dy * dy
return squared and s or math_sqrt(s)
end
function lume.angle(x1, y1, x2, y2)
return math.atan2(y2 - y1, x2 - x1)
return math_atan2(y2 - y1, x2 - x1)
end
function lume.random(a, b)
if not a then a, b = 0, 1 end
if not b then b = 0 end
return a + math.random() * (b - a)
return a + math_random() * (b - a)
end
function lume.randomchoice(t)
return t[math.random(#t)]
return t[math_random(#t)]
end
function lume.weightedchoice(t)
local sum = 0
for k, v in pairs(t) do
assert(v >= 0, "weight value less than zero")
sum = sum + v
end
assert(sum ~= 0, "all weights are zero")
local rnd = lume.random(sum)
for k, v in pairs(t) do
if rnd < v then return k end
rnd = rnd - v
end
end
function lume.shuffle(t)
for i = 1, #t do
local r = math.random(#t)
local r = math_random(#t)
t[i], t[r] = t[r], t[i]
end
return t
@@ -76,7 +139,7 @@ end
function lume.array(...)
local t = {}
for x in unpack({...}) do t[#t + 1] = x end
for x in ... do t[#t + 1] = x end
return t
end
@@ -92,14 +155,15 @@ end
function lume.map(t, fn)
local rtn = {}
fn = iteratee(fn)
local rtn = {}
for k, v in pairs(t) do rtn[k] = fn(v) end
return rtn
end
function lume.all(t, fn)
fn = fn or function(x) return x end
fn = iteratee(fn)
for k, v in pairs(t) do
if not fn(v) then return false end
end
@@ -108,7 +172,7 @@ end
function lume.any(t, fn)
fn = fn or function(x) return x end
fn = iteratee(fn)
for k, v in pairs(t) do
if fn(v) then return true end
end
@@ -117,21 +181,24 @@ end
function lume.reduce(t, fn, first)
for i = 1, #t do first = fn(first, t[i]) end
return first
local acc = first or t[1]
assert(acc, "reduce of an empty array with no first value")
for i = first and 1 or 2, #t do acc = fn(acc, t[i]) end
return acc
end
function lume.set(t, retainkeys)
local tmp = {}
for k, v in pairs(t) do tmp[v] = k end
local rtn = {}
for k, v in pairs(tmp) do rtn[retainkeys and v or (#rtn + 1)] = k end
for k, v in pairs(lume.invert(t)) do
rtn[retainkeys and v or (#rtn + 1)] = k
end
return rtn
end
function lume.filter(t, fn, retainkeys)
fn = iteratee(fn)
local rtn = {}
for k, v in pairs(t) do
if fn(v) then rtn[retainkeys and k or (#rtn + 1)] = v end
@@ -156,13 +223,62 @@ function lume.find(t, value)
end
function lume.slice(t, i, j)
i = i or 1
j = j and (j < 0 and (#t + j + 1) or j) or #t
local rtn = {}
for i = math.max(i, 1), math.min(j, #t) do
rtn[#rtn + 1] = t[i]
function lume.match(t, fn)
fn = iteratee(fn)
for k, v in pairs(t) do
if fn(v) then return v, k end
end
return nil
end
function lume.count(t, fn)
local count = 0
if fn then
fn = iteratee(fn)
for k, v in pairs(t) do
if fn(v) then count = count + 1 end
end
else
for k in pairs(t) do count = count + 1 end
end
return count
end
function lume.slice(t, i, j)
i = i and absindex(#t, i) or 1
j = j and absindex(#t, j) or #t
local rtn = {}
for x = i < 1 and 1 or i, j > #t and #t or j do
rtn[#rtn + 1] = t[x]
end
return rtn
end
function lume.first(t, n)
if not n then return t[1] end
return lume.slice(t, 1, n)
end
function lume.last(t, n)
if not n then return t[#t] end
return lume.slice(t, -n, -1)
end
function lume.invert(t)
local rtn = {}
for k, v in pairs(t) do rtn[v] = k end
return rtn
end
function lume.keys(t)
local rtn = {}
for k, v in pairs(t) do rtn[#rtn + 1] = k end
return rtn
end
@@ -175,10 +291,11 @@ end
function lume.fn(fn, ...)
assert(iscallable(fn), "expected a function as the first argument")
local args = {...}
return function(...)
local a = lume.merge(lume.clone(args), {...})
return fn(unpack(a))
return fn(unpack(a))
end
end
@@ -194,6 +311,39 @@ function lume.once(fn, ...)
end
local memoize_fnkey = {}
local memoize_nil = {}
function lume.memoize(fn)
local cache = {}
return function(...)
local c = cache
for i = 1, select("#", ...) do
local a = select(i, ...) or memoize_nil
c[a] = c[a] or {}
c = c[a]
end
c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
return unpack(c[memoize_fnkey])
end
end
function lume.combine(...)
local funcs = {}
for i = 1, select("#", ...) do
local fn = select(i, ...)
if fn ~= nil then
assert(iscallable(fn), "expected a function or nil")
funcs[#funcs + 1] = fn
end
end
return function(...)
for _, f in ipairs(funcs) do f(...) end
end
end
function lume.time(fn, ...)
local start = os.clock()
local rtn = {fn(...)}
@@ -201,6 +351,19 @@ function lume.time(fn, ...)
end
local lambda_cache = {}
function lume.lambda(str)
if not lambda_cache[str] then
local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
assert(args and body, "bad string lambda")
local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
lambda_cache[str] = lume.dostring(s)
end
return lambda_cache[str]
end
function lume.serialize(x)
local f = { string = function(v) return string.format("%q", v) end,
number = tostring, boolean = tostring }
@@ -211,6 +374,8 @@ function lume.serialize(x)
end
return "{" .. table.concat(rtn) .. "}"
end
local err = function(t,k) error("unsupported serialize type: " .. k) end
setmetatable(f, { __index = err })
return f[type(x)](x)
end
@@ -221,20 +386,27 @@ end
function lume.split(str, sep)
return lume.array(str:gmatch("([^" .. (sep or "%s") .. "]+)"))
if not sep then
return lume.array(str:gmatch("([%S]+)"))
else
assert(sep ~= "", "empty separator")
local psep = patternescape(sep)
return lume.array((str..sep):gmatch("(.-)("..psep..")"))
end
end
function lume.trim(str, chars)
chars = chars or "%s"
if not chars then return str:match("^[%s]*(.-)[%s]*$") end
chars = patternescape(chars)
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
end
function lume.format(str, vars)
if not vars then return str end
local f = function(x)
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
local f = function(x)
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
end
return (str:gsub("{(.-)}", f))
end
@@ -242,8 +414,13 @@ end
function lume.trace(...)
local info = debug.getinfo(2, "Sl")
local head = "[" .. info.short_src .. ":" .. info.currentline .. "] "
print(head .. table.concat(lume.map({...}, tostring), " "))
local t = { "[" .. info.short_src .. ":" .. info.currentline .. "]" }
for i = 1, select("#", ...) do
local x = select(i, ...)
x = (type(x) == "number") and lume.round(x, .01) or (x or "nil")
t[#t + 1] = tostring(x)
end
print(table.concat(t, " "))
end
@@ -252,11 +429,21 @@ function lume.dostring(str)
end
function lume.uuid()
local fn = function(x)
local r = math_random(16) - 1
r = (x == "x") and (r + 1) or (r % 4) + 9
return ("0123456789abcdef"):sub(r, r)
end
return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
end
function lume.hotswap(modname)
local oldglobal = lume.clone(_G)
local updated = {}
local function update(old, new)
if updated[old] then return end
if updated[old] then return end
updated[old] = true
local oldmt, newmt = getmetatable(old), getmetatable(new)
if oldmt and newmt then update(oldmt, newmt) end
@@ -264,18 +451,19 @@ function lume.hotswap(modname)
if type(v) == "table" then update(old[k], v) else old[k] = v end
end
end
local oldmod = pcall(require, modname) or nil
local err = nil
local function onerror(e)
for k, v in pairs(_G) do _G[k] = oldglobal[k] end
err = lume.trim(e)
end
local ok, oldmod = pcall(require, modname)
oldmod = ok and oldmod or nil
xpcall(function()
package.loaded[modname] = nil
local newmod = require(modname)
if type(oldmod) == "table" then update(oldmod, newmod) end
for k, v in pairs(oldglobal) do
if v ~= _G[k] and type(v) == "table" then
if v ~= _G[k] and type(v) == "table" then
update(v, _G[k])
_G[k] = v
end
@@ -288,13 +476,33 @@ end
function lume.rgba(color)
local floor = math.floor
local a = floor((color / 2 ^ 24) % 256)
local r = floor((color / 2 ^ 16) % 256)
local g = floor((color / 2 ^ 08) % 256)
local b = floor((color) % 256)
local a = math_floor((color / 16777216) % 256)
local r = math_floor((color / 65536) % 256)
local g = math_floor((color / 256) % 256)
local b = math_floor((color) % 256)
return r, g, b, a
end
local chain_mt = {}
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
function(fn)
return function(self, ...)
self._value = fn(self._value, ...)
return self
end
end)
chain_mt.__index.result = function(x) return x._value end
function lume.chain(value)
return setmetatable({ _value = value }, chain_mt)
end
setmetatable(lume, {
__call = function(t, ...)
return lume.chain(...)
end
})
return lume

View File

@@ -64,11 +64,11 @@ end
-- lume.distance
tests["lume.distance"] = function()
testeq( lume.distance(10, 20, 10, 20), 0 )
testeq( lume.distance(10, 20, 20, 20), 10 )
local x = lume.distance(1, 2, 5, 7)
testeq( lume.distance(1, 2, 5, 7, true), x * x )
testeq( lume.distance(10, 10, 10, 20, true), 10 * 10 )
testeq( lume.distance(15, 20, 15, 20), 0 )
testeq( lume.distance(13, 44, 156, 232), 236.205419074 )
testeq( lume.distance(-23, 66, -232, 123), 216.633330769 )
local x = lume.distance(13, 15, -2, 81)
testeq( lume.distance(13, 15, -2, 81, true), x * x )
end
-- lume.angle
@@ -82,6 +82,9 @@ end
-- lume.random
tests["lume.random"] = function()
testeq( type(lume.random()), "number" )
testeq( type(lume.random(1)), "number" )
testeq( type(lume.random(1, 2)), "number" )
end
-- lume.randomchoice
@@ -94,6 +97,15 @@ tests["lume.randomchoice"] = function()
testeq( lume.randomchoice({true}), true )
end
-- lume.weightedchoice
tests["lume.weightedchoice"] = function()
testeq( lume.weightedchoice( {a = 1} ), "a" )
testeq( lume.weightedchoice( {a = 0, b = 1} ), "b" )
tester.test.error( lume.weightedchoice, {} )
tester.test.error( lume.weightedchoice, { a = 0, b = 0 } )
tester.test.error( lume.weightedchoice, { a = 1, b = -1 } )
end
-- lume.shuffle
tests["lume.shuffle"] = function()
local t = {1, 2, 3, 4, 5}
@@ -103,7 +115,6 @@ tests["lume.shuffle"] = function()
testeq( lume.shuffle({}), {} )
end
-- lume.array
tests["lume.array"] = function()
local t = lume.array(pairs({a=0, b=0, c=0}))
@@ -130,6 +141,8 @@ end
tests["lume.map"] = function()
testeq( lume.map({1, 2, 3}, function(x) return x * 2 end), {2, 4, 6} )
testeq( lume.map({a=2,b=3}, function(x) return x * 2 end), {a=4,b=6} )
local t = {{ id = 10 }, { id = 20 }, { id = 30 }}
testeq( lume.map(t, "id"), { 10, 20, 30 })
end
-- lume.all
@@ -138,6 +151,10 @@ tests["lume.all"] = function()
testeq( lume.all({true, true, true, true}), true )
testeq( lume.all({2, 3, 4, 5}, function(x) return x % 2 == 0 end), false )
testeq( lume.all({2, 4, 6, 8}, function(x) return x % 2 == 0 end), true )
testeq( lume.all({{ x = 1 }, {}, { x = 3 }}, "x"), false )
testeq( lume.all({{ x = 1 }, { x = 2 }, { x = 3 }}, "x"), true )
testeq( lume.all({{ x = 1 }, { x = 2 }, { x = 3 }}, { x = 2 }), false )
testeq( lume.all({{ x = 2 }, { x = 2 }, { x = 2 }}, { x = 2 }), true )
end
-- lume.any
@@ -146,17 +163,27 @@ tests["lume.any"] = function()
testeq( lume.any({false, false, false}), false )
testeq( lume.any({2, 3, 4, 5}, function(x) return x % 2 == 0 end), true )
testeq( lume.any({1, 3, 5, 7}, function(x) return x % 2 == 0 end), false )
local t = {{ id = 10 }, { id = 20 }, { id = 30 }}
testeq( lume.any(t, { id = 10 }), true )
testeq( lume.any(t, { id = 40 }), false )
end
-- lume.reduce
tests["lume.reduce"] = function()
local concat = function(a, b) return a .. b end
local add = function(a, b) return a + b end
testeq( lume.reduce({"cat", "dog"}, concat, ""), "catdog" )
testeq( lume.reduce({"cat", "dog"}, concat, "pig"), "pigcatdog" )
testeq( lume.reduce({"me", "ow"}, concat), "meow" )
testeq( lume.reduce({1, 2, 3, 4}, add), 10 )
testeq( lume.reduce({1, 2, 3, 4}, add, 5), 15 )
testeq( lume.reduce({1}, add), 1 )
testeq( lume.reduce({}, concat, "potato"), "potato" )
end
-- lume.set
tests["lume.set"] = function()
testeq( lume.set({}), {} )
local t = lume.set({1, 2, 3, 2, 5, 6, 6})
table.sort(t)
testeq( t, {1, 2, 3, 5, 6} )
@@ -171,6 +198,8 @@ tests["lume.filter"] = function()
testeq( t, {2, 4} )
local t = lume.filter({a=1, b=2, c=3}, function(x) return x == 2 end, true)
testeq( t, {b=2} )
local t = lume.filter({{ x=1, y=1 }, { x=2, y=2 }, { x=1, y=3 }}, { x = 1 })
testeq( t, {{ x=1, y=1 }, {x=1, y=3}} )
end
-- lume.merge
@@ -186,15 +215,89 @@ tests["lume.find"] = function()
testeq( lume.find({a=1, b=5, c=7}, 5), "b" )
end
-- lume.match
tests["lume.match"] = function()
local t = { "a", "b", "c", "d" }
local t2 = { a = 1, b = 2, c = 3, d = 4 }
local t3 = { {x=1, y=2}, {x=3, y=4}, {x=5, y=6} }
local v, k = lume.match(t, function(x) return x > "c" end)
testeq( v, "d" )
testeq( k, 4 )
local v, k = lume.match(t, function(x) return x < "b" end)
testeq( v, "a" )
testeq( k, 1 )
local v, k = lume.match(t2, function(x) return x < 2 end)
testeq( v, 1 )
testeq( k, "a" )
local v, k = lume.match(t2, function(x) return x > 5 end)
testeq( v, nil )
testeq( k, nil )
local v, k = lume.match(t3, { x = 3, y = 4 })
testeq( k, 2 )
end
-- lume.count
tests["lume.count"] = function()
local t = { a = 1, b = 2, c = 5, [13] = 22, z = 8 }
testeq( lume.count(t), 5 )
testeq( lume.count(t, function(x) return x % 2 == 0 end ), 3 )
local a = { 5, 6, 7, 8, 9 }
testeq( lume.count(a), #a )
local t = { { n = 20 }, { n = 30 }, { n = 40 }, { n = 20 } }
testeq( lume.count(t, { n = 20 }), 2 )
testeq( lume.count(t, { n = 30 }), 1 )
testeq( lume.count(t, { n = 50 }), 0 )
end
-- lume.slice
tests["lume.slice"] = function()
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 4), {"b", "c", "d"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, -2), {"b", "c", "d"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 3, -1), {"c", "d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 3), {"c", "d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 4), {"d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 1, 1), {"a"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 1), {} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 4), {"b", "c", "d"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, -2), {"b", "c", "d"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 3, -1), {"c", "d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 3), {"c", "d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 4), {"d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 1, 1), {"a"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 1), {} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, -2), {"c", "d"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, 1), {} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 0, 1), {"a"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, 0, 0), {} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, -3), {"c", "d", "e"} )
testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, 900), {"c", "d", "e"} )
end
-- lume.first
tests["lume.first"] = function()
local t = { "a", "b", "c", "d", "e" }
testeq( lume.first(t), "a" )
testeq( lume.first(t, 1), { "a" } )
testeq( lume.first(t, 2), { "a", "b" } )
end
-- lume.last
tests["lume.last"] = function()
local t = { "a", "b", "c", "d", "e" }
testeq( lume.last(t), "e" )
testeq( lume.last(t, 1), { "e" } )
testeq( lume.last(t, 2), { "d", "e" } )
end
-- lume.invert
tests["lume.invert"] = function()
testeq( lume.invert({}), {} )
testeq( lume.invert{a = "x", b = "y"}, {x = "a", y = "b"} )
testeq( lume.invert{a = 1, b = 2}, {"a", "b"} )
testeq( lume.invert(lume.invert{a = 1, b = 2}), {a = 1, b = 2} )
end
-- lume.keys
tests["lume.keys"] = function()
testeq( lume.keys({}), {} )
local t = lume.keys({ aaa = 1, bbb = 2, ccc = 3 })
table.sort(t)
testeq( t, {"aaa", "bbb", "ccc"} )
local t = lume.keys({ "x", "x", "x" })
testeq( t, {1, 2, 3} )
end
-- lume.clone
@@ -209,6 +312,7 @@ end
tests["lume.fn"] = function()
local f = lume.fn(function(a, b) return a + b end, 10)
testeq( f(5), 15 )
tester.test.error( lume.fn, 123 )
end
-- lume.once
@@ -216,6 +320,35 @@ tests["lume.once"] = function()
local f = lume.once(function(a, b) return a + b end, 10)
testeq( f(5), 15 )
testeq( f(5), nil )
tester.test.error( lume.once, 123 )
end
-- lume.memoize
tests["lume.memoize"] = function()
local f = lume.memoize(
function(a, b, c)
return tostring(a) .. tostring(b) .. tostring(c)
end)
testeq( f("hello", nil, 15), "hellonil15" )
testeq( f("hello", nil, 15), "hellonil15" )
testeq( f(), "nilnilnil" )
testeq( f(), "nilnilnil" )
local f2 = lume.memoize(function() end)
testeq( f2(), nil )
end
-- lume.combine
tests["lume.combine"] = function()
local acc = 0
local a = function(x, y) acc = acc + x + y end
local b = function(x, y) acc = acc + x * y end
local fn = lume.combine(a, b)
fn(10, 20)
testeq( acc, 230 )
acc = 0
fn = lume.combine(nil, a, nil, b, nil)
fn(10, 20)
testeq( acc, 230 )
end
-- lume.time
@@ -225,6 +358,21 @@ tests["lume.time"] = function()
testeq( {a, b, c}, {50, 60, 70} )
end
-- lume.lambda
tests["lume.lambda"] = function()
testeq( lume.lambda "x->x*x"(10), 100 )
testeq( lume.lambda "x->x*x"(20), 400 )
testeq( lume.lambda "x,y -> 2*x+y"(10,5), 25 )
testeq( lume.lambda "a, b -> a / b"(1, 2), .5 )
testeq( lume.lambda "a -> 'hi->' .. a"("doggy"), "hi->doggy" )
testeq( lume.lambda "A1,_->A1.._"("te","st"), "test" )
testeq( lume.lambda "->"(1,2,3), nil )
tester.test.error( lume.lambda, "abc" )
tester.test.error( lume.lambda, "" )
tester.test.error( lume.lambda, "a,b->a->b" )
tester.test.error( lume.lambda, "(a),b->a+b" )
end
-- lume.serialize / lume.deserialize
tests["lume.serialize, lume.deserialize"] = function()
local t = { 1, 2, 3, 4, true, false, "cat", "dog", {1, 2, 3} }
@@ -234,9 +382,15 @@ end
-- lume.split
tests["lume.split"] = function()
testeq( lume.split("cat dog pig"), {"cat", "dog", "pig"} )
testeq( lume.split(",cat,dog,pig", ","), {"cat", "dog", "pig"} )
testeq( lume.split(",cat,dog;pig", ",;"), {"cat", "dog", "pig"} )
testeq( lume.split("cat dog pig"), {"cat", "dog", "pig"} )
testeq( lume.split("cat,dog,pig", ","), {"cat", "dog", "pig"} )
testeq( lume.split("cat,dog;pig", ";"), {"cat,dog", "pig"} )
testeq( lume.split("cat,dog,,pig", ","), {"cat", "dog", "", "pig"} )
testeq( lume.split(";;;cat;", ";"), {"", "", "", "cat", ""} )
testeq( lume.split("cat.dog", "."), {"cat", "dog"} )
testeq( lume.split("cat%dog", "%"), {"cat", "dog"} )
testeq( lume.split("1<>2<>3", "<>"), {"1", "2", "3"} )
tester.test.error( lume.split, "abc", "" )
end
-- lume.trim
@@ -244,6 +398,8 @@ tests["lume.trim"] = function()
testeq( lume.trim(" hello world "), "hello world" )
testeq( lume.trim("-=-hello-world===", "-="), "hello-world" )
testeq( lume.trim("***hello world*-*", "*"), "hello world*-" )
testeq( lume.trim("...hello world.", "."), "hello world" )
testeq( lume.trim("^.hello world]^", "^.]"), "hello world" )
end
-- lume.format
@@ -265,11 +421,11 @@ tests["lume.trace"] = function()
print = function(x)
file, line, msg = x:match("%[(.-):(.-)%] (.*)")
end
lume.trace("Hi world")
lume.trace("Hi world", 123.456, 1, nil)
print = oldprint
testeq( file:match(".lua$"), ".lua" )
testeq( tonumber(line) ~= nil, true )
testeq( msg, "Hi world" )
testeq( file:match(".lua$"), ".lua" )
testeq( tonumber(line) ~= nil, true )
testeq( msg, "Hi world 123.46 1 nil" )
end
-- lume.dostring
@@ -278,6 +434,12 @@ tests["lume.dostring"] = function()
testeq( lume.dostring([[return 12345]]), 12345 )
end
-- lume.uuid
tests["lume.uuid"] = function()
testeq( type(lume.uuid()), "string" )
testeq( #lume.uuid(), 36 )
end
-- lume.hotswap
tests["lume.hotswap"] = function()
local ok, err = lume.hotswap("bad_module_name")
@@ -294,6 +456,15 @@ tests["lume.rgba"] = function()
testeq( b, 0x78 )
end
-- lume.chain
tests["lume.chain"] = function()
local t = lume.chain({1, 2}):map(function(x) return x * 2 end):result()
testeq( t, { 2, 4 } )
testeq( lume.chain(10):result(), 10 )
local t = lume({1, 2}):map(function(x) return x * 2 end):result()
testeq( t, { 2, 4 } )
end
tester.dotests(tests)
tester.test.global()

View File

@@ -128,6 +128,18 @@ function tester.test.equal(result, expected)
end
function tester.test.error(fn, ...)
local passed = not pcall(fn, ...)
local info = debug.getinfo(2)
if passed then
dopass(info.short_src, info.currentline)
else
dofail(info.short_src, info.currentline)
printfailmsg("Expected an error to be raised")
end
end
function tester.dotests(t)
local keys = {}
for k in pairs(t) do table.insert(keys, k) end