├── LICENSE ├── README.md ├── lume.lua └── test ├── test.lua └── util └── tester.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 rxi 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lume 2 | 3 | A collection of functions for Lua, geared towards game development. 4 | 5 | 6 | ## Installation 7 | 8 | The [lume.lua](lume.lua?raw=1) file should be dropped into an existing project 9 | and required by it: 10 | 11 | ```lua 12 | lume = require "lume" 13 | ``` 14 | 15 | 16 | ## Function Reference 17 | 18 | #### lume.clamp(x, min, max) 19 | Returns the number `x` clamped between the numbers `min` and `max` 20 | 21 | #### lume.round(x [, increment]) 22 | Rounds `x` to the nearest integer; rounds away from zero if we're midway 23 | between two integers. If `increment` is set then the number is rounded to the 24 | nearest increment. 25 | ```lua 26 | lume.round(2.3) -- Returns 2 27 | lume.round(123.4567, .1) -- Returns 123.5 28 | ``` 29 | 30 | #### lume.sign(x) 31 | Returns `1` if `x` is 0 or above, returns `-1` when `x` is negative. 32 | 33 | #### lume.lerp(a, b, amount) 34 | Returns the linearly interpolated number between `a` and `b`, `amount` should 35 | be in the range of 0 - 1; if `amount` is outside of this range it is clamped. 36 | ```lua 37 | lume.lerp(100, 200, .5) -- Returns 150 38 | ``` 39 | 40 | #### lume.smooth(a, b, amount) 41 | Similar to `lume.lerp()` but uses cubic interpolation instead of linear 42 | interpolation. 43 | 44 | #### lume.pingpong(x) 45 | Ping-pongs the number `x` between 0 and 1. 46 | 47 | #### lume.distance(x1, y1, x2, y2 [, squared]) 48 | Returns the distance between the two points. If `squared` is true then the 49 | squared distance is returned -- this is faster to calculate and can still be 50 | used when comparing distances. 51 | 52 | #### lume.angle(x1, y1, x2, y2) 53 | Returns the angle between the two points. 54 | 55 | #### lume.vector(angle, magnitude) 56 | Given an `angle` and `magnitude`, returns a vector. 57 | ```lua 58 | local x, y = lume.vector(0, 10) -- Returns 10, 0 59 | ``` 60 | 61 | #### lume.random([a [, b]]) 62 | Returns a random number between `a` and `b`. If only `a` is supplied a number 63 | between `0` and `a` is returned. If no arguments are supplied a random number 64 | between `0` and `1` is returned. 65 | 66 | #### lume.randomchoice(t) 67 | Returns a random value from array `t`. If the array is empty an error is 68 | raised. 69 | ```lua 70 | lume.randomchoice({true, false}) -- Returns either true or false 71 | ``` 72 | 73 | #### lume.weightedchoice(t) 74 | Takes the argument table `t` where the keys are the possible choices and the 75 | value is the choice's weight. A weight should be 0 or above, the larger the 76 | number the higher the probability of that choice being picked. If the table is 77 | empty, a weight is below zero or all the weights are 0 then an error is raised. 78 | ```lua 79 | lume.weightedchoice({ ["cat"] = 10, ["dog"] = 5, ["frog"] = 0 }) 80 | -- Returns either "cat" or "dog" with "cat" being twice as likely to be chosen. 81 | ``` 82 | 83 | #### lume.isarray(x) 84 | Returns `true` if `x` is an array -- the value is assumed to be an array if it 85 | is a table which contains a value at the index `1`. This function is used 86 | internally and can be overridden if you wish to use a different method to detect 87 | arrays. 88 | 89 | 90 | #### lume.push(t, ...) 91 | Pushes all the given values to the end of the table `t` and returns the pushed 92 | values. Nil values are ignored. 93 | ```lua 94 | local t = { 1, 2, 3 } 95 | lume.push(t, 4, 5) -- `t` becomes { 1, 2, 3, 4, 5 } 96 | ``` 97 | 98 | #### lume.remove(t, x) 99 | Removes the first instance of the value `x` if it exists in the table `t`. 100 | Returns `x`. 101 | ```lua 102 | local t = { 1, 2, 3 } 103 | lume.remove(t, 2) -- `t` becomes { 1, 3 } 104 | ``` 105 | 106 | #### lume.clear(t) 107 | Nils all the values in the table `t`, this renders the table empty. Returns 108 | `t`. 109 | ```lua 110 | local t = { 1, 2, 3 } 111 | lume.clear(t) -- `t` becomes {} 112 | ``` 113 | 114 | #### lume.extend(t, ...) 115 | Copies all the fields from the source tables to the table `t` and returns `t`. 116 | If a key exists in multiple tables the right-most table's value is used. 117 | ```lua 118 | local t = { a = 1, b = 2 } 119 | lume.extend(t, { b = 4, c = 6 }) -- `t` becomes { a = 1, b = 4, c = 6 } 120 | ``` 121 | 122 | #### lume.shuffle(t) 123 | Returns a shuffled copy of the array `t`. 124 | 125 | #### lume.sort(t [, comp]) 126 | Returns a copy of the array `t` with all its items sorted. If `comp` is a 127 | function it will be used to compare the items when sorting. If `comp` is a 128 | string it will be used as the key to sort the items by. 129 | ```lua 130 | lume.sort({ 1, 4, 3, 2, 5 }) -- Returns { 1, 2, 3, 4, 5 } 131 | lume.sort({ {z=2}, {z=3}, {z=1} }, "z") -- Returns { {z=1}, {z=2}, {z=3} } 132 | lume.sort({ 1, 3, 2 }, function(a, b) return a > b end) -- Returns { 3, 2, 1 } 133 | ``` 134 | 135 | #### lume.array(...) 136 | Iterates the supplied iterator and returns an array filled with the values. 137 | ```lua 138 | lume.array(string.gmatch("Hello world", "%a+")) -- Returns {"Hello", "world"} 139 | ``` 140 | 141 | #### lume.each(t, fn, ...) 142 | Iterates the table `t` and calls the function `fn` on each value followed by 143 | the supplied additional arguments; if `fn` is a string the method of that name 144 | is called for each value. The function returns `t` unmodified. 145 | ```lua 146 | lume.each({1, 2, 3}, print) -- Prints "1", "2", "3" on separate lines 147 | lume.each({a, b, c}, "move", 10, 20) -- Does x:move(10, 20) on each value 148 | ``` 149 | 150 | #### lume.map(t, fn) 151 | Applies the function `fn` to each value in table `t` and returns a new table 152 | with the resulting values. 153 | ```lua 154 | lume.map({1, 2, 3}, function(x) return x * 2 end) -- Returns {2, 4, 6} 155 | ``` 156 | 157 | #### lume.all(t [, fn]) 158 | Returns true if all the values in `t` table are true. If a `fn` function is 159 | supplied it is called on each value, true is returned if all of the calls to 160 | `fn` return true. 161 | ```lua 162 | lume.all({1, 2, 1}, function(x) return x == 1 end) -- Returns false 163 | ``` 164 | 165 | #### lume.any(t [, fn]) 166 | Returns true if any of the values in `t` table are true. If a `fn` function is 167 | supplied it is called on each value, true is returned if any of the calls to 168 | `fn` return true. 169 | ```lua 170 | lume.any({1, 2, 1}, function(x) return x == 1 end) -- Returns true 171 | ``` 172 | 173 | #### lume.reduce(t, fn [, first]) 174 | Applies `fn` on two arguments cumulative to the items of the array `t`, from 175 | left to right, so as to reduce the array to a single value. If a `first` value 176 | is specified the accumulator is initialised to this, otherwise the first value 177 | in the array is used. If the array is empty and no `first` value is specified 178 | an error is raised. 179 | ```lua 180 | lume.reduce({1, 2, 3}, function(a, b) return a + b end) -- Returns 6 181 | ``` 182 | 183 | #### lume.unique(t) 184 | Returns a copy of the `t` array with all the duplicate values removed. 185 | ```lua 186 | lume.unique({2, 1, 2, "cat", "cat"}) -- Returns {1, 2, "cat"} 187 | ``` 188 | 189 | #### lume.filter(t, fn [, retainkeys]) 190 | Calls `fn` on each value of `t` table. Returns a new table with only the values 191 | where `fn` returned true. If `retainkeys` is true the table is not treated as 192 | an array and retains its original keys. 193 | ```lua 194 | lume.filter({1, 2, 3, 4}, function(x) return x % 2 == 0 end) -- Returns {2, 4} 195 | ``` 196 | 197 | #### lume.reject(t, fn [, retainkeys]) 198 | The opposite of `lume.filter()`: Calls `fn` on each value of `t` table; returns 199 | a new table with only the values where `fn` returned false. If `retainkeys` is 200 | true the table is not treated as an array and retains its original keys. 201 | ```lua 202 | lume.reject({1, 2, 3, 4}, function(x) return x % 2 == 0 end) -- Returns {1, 3} 203 | ``` 204 | 205 | #### lume.merge(...) 206 | Returns a new table with all the given tables merged together. If a key exists 207 | in multiple tables the right-most table's value is used. 208 | ```lua 209 | lume.merge({a=1, b=2, c=3}, {c=8, d=9}) -- Returns {a=1, b=2, c=8, d=9} 210 | ``` 211 | 212 | #### lume.concat(...) 213 | Returns a new array consisting of all the given arrays concatenated into one. 214 | ```lua 215 | lume.concat({1, 2}, {3, 4}, {5, 6}) -- Returns {1, 2, 3, 4, 5, 6} 216 | ``` 217 | 218 | #### lume.find(t, value) 219 | Returns the index/key of `value` in `t`. Returns `nil` if that value does not 220 | exist in the table. 221 | ```lua 222 | lume.find({"a", "b", "c"}, "b") -- Returns 2 223 | ``` 224 | 225 | #### lume.match(t, fn) 226 | Returns the value and key of the value in table `t` which returns true when 227 | `fn` is called on it. Returns `nil` if no such value exists. 228 | ```lua 229 | lume.match({1, 5, 8, 7}, function(x) return x % 2 == 0 end) -- Returns 8, 3 230 | ``` 231 | 232 | #### lume.count(t [, fn]) 233 | Counts the number of values in the table `t`. If a `fn` function is supplied it 234 | is called on each value, the number of times it returns true is counted. 235 | ```lua 236 | lume.count({a = 2, b = 3, c = 4, d = 5}) -- Returns 4 237 | lume.count({1, 2, 4, 6}, function(x) return x % 2 == 0 end) -- Returns 3 238 | ``` 239 | 240 | #### lume.slice(t [, i [, j]]) 241 | Mimics the behaviour of Lua's `string.sub`, but operates on an array rather 242 | than a string. Creates and returns a new array of the given slice. 243 | ```lua 244 | lume.slice({"a", "b", "c", "d", "e"}, 2, 4) -- Returns {"b", "c", "d"} 245 | ``` 246 | 247 | #### lume.first(t [, n]) 248 | Returns the first element of an array or nil if the array is empty. If `n` is 249 | specificed an array of the first `n` elements is returned. 250 | ```lua 251 | lume.first({"a", "b", "c"}) -- Returns "a" 252 | ``` 253 | 254 | #### lume.last(t [, n]) 255 | Returns the last element of an array or nil if the array is empty. If `n` is 256 | specificed an array of the last `n` elements is returned. 257 | ```lua 258 | lume.last({"a", "b", "c"}) -- Returns "c" 259 | ``` 260 | 261 | #### lume.invert(t) 262 | Returns a copy of the table where the keys have become the values and the 263 | values the keys. 264 | ```lua 265 | lume.invert({a = "x", b = "y"}) -- returns {x = "a", y = "b"} 266 | ``` 267 | 268 | #### lume.pick(t, ...) 269 | Returns a copy of the table filtered to only contain values for the given keys. 270 | ```lua 271 | lume.pick({ a = 1, b = 2, c = 3 }, "a", "c") -- Returns { a = 1, c = 3 } 272 | ``` 273 | 274 | #### lume.keys(t) 275 | Returns an array containing each key of the table. 276 | 277 | #### lume.clone(t) 278 | Returns a shallow copy of the table `t`. 279 | 280 | #### lume.fn(fn, ...) 281 | Creates a wrapper function around function `fn`, automatically inserting the 282 | arguments into `fn` which will persist every time the wrapper is called. Any 283 | arguments which are passed to the returned function will be inserted after the 284 | already existing arguments passed to `fn`. 285 | ```lua 286 | local f = lume.fn(print, "Hello") 287 | f("world") -- Prints "Hello world" 288 | ``` 289 | 290 | #### lume.once(fn, ...) 291 | Returns a wrapper function to `fn` which takes the supplied arguments. The 292 | wrapper function will call `fn` on the first call and do nothing on any 293 | subsequent calls. 294 | ```lua 295 | local f = lume.once(print, "Hello") 296 | f() -- Prints "Hello" 297 | f() -- Does nothing 298 | ``` 299 | 300 | #### lume.memoize(fn) 301 | Returns a wrapper function to `fn` where the results for any given set of 302 | arguments are cached. `lume.memoize()` is useful when used on functions with 303 | slow-running computations. 304 | ```lua 305 | fib = lume.memoize(function(n) return n < 2 and n or fib(n-1) + fib(n-2) end) 306 | ``` 307 | 308 | #### lume.combine(...) 309 | Creates a wrapper function which calls each supplied argument in the order they 310 | were passed to `lume.combine()`; nil arguments are ignored. The wrapper 311 | function passes its own arguments to each of its wrapped functions when it is 312 | called. 313 | ```lua 314 | local f = lume.combine(function(a, b) print(a + b) end, 315 | function(a, b) print(a * b) end) 316 | f(3, 4) -- Prints "7" then "12" on a new line 317 | ``` 318 | 319 | #### lume.call(fn, ...) 320 | Calls the given function with the provided arguments and returns its values. If 321 | `fn` is `nil` then no action is performed and the function returns `nil`. 322 | ```lua 323 | lume.call(print, "Hello world") -- Prints "Hello world" 324 | ``` 325 | 326 | #### lume.time(fn, ...) 327 | Inserts the arguments into function `fn` and calls it. Returns the time in 328 | seconds the function `fn` took to execute followed by `fn`'s returned values. 329 | ```lua 330 | lume.time(function(x) return x end, "hello") -- Returns 0, "hello" 331 | ``` 332 | 333 | #### lume.lambda(str) 334 | Takes a string lambda and returns a function. `str` should be a list of 335 | comma-separated parameters, followed by `->`, followed by the expression which 336 | will be evaluated and returned. 337 | ```lua 338 | local f = lume.lambda "x,y -> 2*x+y" 339 | f(10, 5) -- Returns 25 340 | ``` 341 | 342 | #### lume.serialize(x) 343 | Serializes the argument `x` into a string which can be loaded again using 344 | `lume.deserialize()`. Only booleans, numbers, tables and strings can be 345 | serialized. Circular references will result in an error; all nested tables are 346 | serialized as unique tables. 347 | ```lua 348 | lume.serialize({a = "test", b = {1, 2, 3}, false}) 349 | -- Returns "{[1]=false,["a"]="test",["b"]={[1]=1,[2]=2,[3]=3,},}" 350 | ``` 351 | 352 | #### lume.deserialize(str) 353 | Deserializes a string created by `lume.serialize()` and returns the resulting 354 | value. This function should not be run on an untrusted string. 355 | ```lua 356 | lume.deserialize("{1, 2, 3}") -- Returns {1, 2, 3} 357 | ``` 358 | 359 | #### lume.split(str [, sep]) 360 | Returns an array of the words in the string `str`. If `sep` is provided it is 361 | used as the delimiter, consecutive delimiters are not grouped together and will 362 | delimit empty strings. 363 | ```lua 364 | lume.split("One two three") -- Returns {"One", "two", "three"} 365 | lume.split("a,b,,c", ",") -- Returns {"a", "b", "", "c"} 366 | ``` 367 | 368 | #### lume.trim(str [, chars]) 369 | Trims the whitespace from the start and end of the string `str` and returns the 370 | new string. If a `chars` value is set the characters in `chars` are trimmed 371 | instead of whitespace. 372 | ```lua 373 | lume.trim(" Hello ") -- Returns "Hello" 374 | ``` 375 | 376 | #### lume.wordwrap(str [, limit]) 377 | Returns `str` wrapped to `limit` number of characters per line, by default 378 | `limit` is `72`. `limit` can also be a function which when passed a string, 379 | returns `true` if it is too long for a single line. 380 | ```lua 381 | -- Returns "Hello world\nThis is a\nshort string" 382 | lume.wordwrap("Hello world. This is a short string", 14) 383 | ``` 384 | 385 | #### lume.format(str [, vars]) 386 | Returns a formatted string. The values of keys in the table `vars` can be 387 | inserted into the string by using the form `"{key}"` in `str`; numerical keys 388 | can also be used. 389 | ```lua 390 | lume.format("{b} hi {a}", {a = "mark", b = "Oh"}) -- Returns "Oh hi mark" 391 | lume.format("Hello {1}!", {"world"}) -- Returns "Hello world!" 392 | ``` 393 | 394 | #### lume.trace(...) 395 | Prints the current filename and line number followed by each argument separated 396 | by a space. 397 | ```lua 398 | -- Assuming the file is called "example.lua" and the next line is 12: 399 | lume.trace("hello", 1234) -- Prints "example.lua:12: hello 1234" 400 | ``` 401 | 402 | #### lume.dostring(str) 403 | Executes the lua code inside `str`. 404 | ```lua 405 | lume.dostring("print('Hello!')") -- Prints "Hello!" 406 | ``` 407 | 408 | #### lume.uuid() 409 | Generates a random UUID string; version 4 as specified in 410 | [RFC 4122](http://www.ietf.org/rfc/rfc4122.txt). 411 | 412 | #### lume.hotswap(modname) 413 | Reloads an already loaded module in place, allowing you to immediately see the 414 | effects of code changes without having to restart the program. `modname` should 415 | be the same string used when loading the module with require(). In the case of 416 | an error the global environment is restored and `nil` plus an error message is 417 | returned. 418 | ```lua 419 | lume.hotswap("lume") -- Reloads the lume module 420 | assert(lume.hotswap("inexistant_module")) -- Raises an error 421 | ``` 422 | 423 | #### lume.ripairs(t) 424 | Performs the same function as `ipairs()` but iterates in reverse; this allows 425 | the removal of items from the table during iteration without any items being 426 | skipped. 427 | ```lua 428 | -- Prints "3->c", "2->b" and "1->a" on separate lines 429 | for i, v in lume.ripairs({ "a", "b", "c" }) do 430 | print(i .. "->" .. v) 431 | end 432 | ``` 433 | 434 | #### lume.color(str [, mul]) 435 | Takes color string `str` and returns 4 values, one for each color channel (`r`, 436 | `g`, `b` and `a`). By default the returned values are between 0 and 1; the 437 | values are multiplied by the number `mul` if it is provided. 438 | ```lua 439 | lume.color("#ff0000") -- Returns 1, 0, 0, 1 440 | lume.color("rgba(255, 0, 255, .5)") -- Returns 1, 0, 1, .5 441 | lume.color("#00ffff", 256) -- Returns 0, 256, 256, 256 442 | lume.color("rgb(255, 0, 0)", 256) -- Returns 256, 0, 0, 256 443 | ``` 444 | 445 | #### lume.chain(value) 446 | Returns a wrapped object which allows chaining of lume functions. The function 447 | result() should be called at the end of the chain to return the resulting 448 | value. 449 | ```lua 450 | lume.chain({1, 2, 3, 4}) 451 | :filter(function(x) return x % 2 == 0 end) 452 | :map(function(x) return -x end) 453 | :result() -- Returns { -2, -4 } 454 | ``` 455 | The table returned by the `lume` module, when called, acts in the same manner 456 | as calling `lume.chain()`. 457 | ```lua 458 | lume({1, 2, 3}):each(print) -- Prints 1, 2 then 3 on separate lines 459 | ``` 460 | 461 | ## Iteratee functions 462 | Several lume functions allow a `table`, `string` or `nil` to be used in place 463 | of their iteratee function argument. The functions that provide this behaviour 464 | are: `map()`, `all()`, `any()`, `filter()`, `reject()`, `match()` and 465 | `count()`. 466 | 467 | If the argument is `nil` then each value will return itself. 468 | ```lua 469 | lume.filter({ true, true, false, true }, nil) -- { true, true, true } 470 | ``` 471 | 472 | If the argument is a `string` then each value will be assumed to be a table, 473 | and will return the value of the key which matches the string. 474 | ``` lua 475 | local t = {{ z = "cat" }, { z = "dog" }, { z = "owl" }} 476 | lume.map(t, "z") -- Returns { "cat", "dog", "owl" } 477 | ``` 478 | 479 | If the argument is a `table` then each value will return `true` or `false`, 480 | depending on whether the values at each of the table's keys match the 481 | collection's value's values. 482 | ```lua 483 | local t = { 484 | { age = 10, type = "cat" }, 485 | { age = 8, type = "dog" }, 486 | { age = 10, type = "owl" }, 487 | } 488 | lume.count(t, { age = 10 }) -- returns 2 489 | ``` 490 | 491 | 492 | ## License 493 | 494 | This library is free software; you can redistribute it and/or modify it under 495 | the terms of the MIT license. See [LICENSE](LICENSE) for details. 496 | -------------------------------------------------------------------------------- /lume.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- lume 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local lume = { _version = "2.3.0" } 26 | 27 | local pairs, ipairs = pairs, ipairs 28 | local type, assert, unpack = type, assert, unpack or table.unpack 29 | local tostring, tonumber = tostring, tonumber 30 | local math_floor = math.floor 31 | local math_ceil = math.ceil 32 | local math_atan2 = math.atan2 or math.atan 33 | local math_sqrt = math.sqrt 34 | local math_abs = math.abs 35 | 36 | local noop = function() 37 | end 38 | 39 | local identity = function(x) 40 | return x 41 | end 42 | 43 | local patternescape = function(str) 44 | return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") 45 | end 46 | 47 | local absindex = function(len, i) 48 | return i < 0 and (len + i + 1) or i 49 | end 50 | 51 | local iscallable = function(x) 52 | if type(x) == "function" then return true end 53 | local mt = getmetatable(x) 54 | return mt and mt.__call ~= nil 55 | end 56 | 57 | local getiter = function(x) 58 | if lume.isarray(x) then 59 | return ipairs 60 | elseif type(x) == "table" then 61 | return pairs 62 | end 63 | error("expected table", 3) 64 | end 65 | 66 | local iteratee = function(x) 67 | if x == nil then return identity end 68 | if iscallable(x) then return x end 69 | if type(x) == "table" then 70 | return function(z) 71 | for k, v in pairs(x) do 72 | if z[k] ~= v then return false end 73 | end 74 | return true 75 | end 76 | end 77 | return function(z) return z[x] end 78 | end 79 | 80 | 81 | 82 | function lume.clamp(x, min, max) 83 | return x < min and min or (x > max and max or x) 84 | end 85 | 86 | 87 | function lume.round(x, increment) 88 | if increment then return lume.round(x / increment) * increment end 89 | return x >= 0 and math_floor(x + .5) or math_ceil(x - .5) 90 | end 91 | 92 | 93 | function lume.sign(x) 94 | return x < 0 and -1 or 1 95 | end 96 | 97 | 98 | function lume.lerp(a, b, amount) 99 | return a + (b - a) * lume.clamp(amount, 0, 1) 100 | end 101 | 102 | 103 | function lume.smooth(a, b, amount) 104 | local t = lume.clamp(amount, 0, 1) 105 | local m = t * t * (3 - 2 * t) 106 | return a + (b - a) * m 107 | end 108 | 109 | 110 | function lume.pingpong(x) 111 | return 1 - math_abs(1 - x % 2) 112 | end 113 | 114 | 115 | function lume.distance(x1, y1, x2, y2, squared) 116 | local dx = x1 - x2 117 | local dy = y1 - y2 118 | local s = dx * dx + dy * dy 119 | return squared and s or math_sqrt(s) 120 | end 121 | 122 | 123 | function lume.angle(x1, y1, x2, y2) 124 | return math_atan2(y2 - y1, x2 - x1) 125 | end 126 | 127 | 128 | function lume.vector(angle, magnitude) 129 | return math.cos(angle) * magnitude, math.sin(angle) * magnitude 130 | end 131 | 132 | 133 | function lume.random(a, b) 134 | if not a then a, b = 0, 1 end 135 | if not b then b = 0 end 136 | return a + math.random() * (b - a) 137 | end 138 | 139 | 140 | function lume.randomchoice(t) 141 | return t[math.random(#t)] 142 | end 143 | 144 | 145 | function lume.weightedchoice(t) 146 | local sum = 0 147 | for _, v in pairs(t) do 148 | assert(v >= 0, "weight value less than zero") 149 | sum = sum + v 150 | end 151 | assert(sum ~= 0, "all weights are zero") 152 | local rnd = lume.random(sum) 153 | for k, v in pairs(t) do 154 | if rnd < v then return k end 155 | rnd = rnd - v 156 | end 157 | end 158 | 159 | 160 | function lume.isarray(x) 161 | return type(x) == "table" and x[1] ~= nil 162 | end 163 | 164 | 165 | function lume.push(t, ...) 166 | local n = select("#", ...) 167 | for i = 1, n do 168 | t[#t + 1] = select(i, ...) 169 | end 170 | return ... 171 | end 172 | 173 | 174 | function lume.remove(t, x) 175 | local iter = getiter(t) 176 | for i, v in iter(t) do 177 | if v == x then 178 | if lume.isarray(t) then 179 | table.remove(t, i) 180 | break 181 | else 182 | t[i] = nil 183 | break 184 | end 185 | end 186 | end 187 | return x 188 | end 189 | 190 | 191 | function lume.clear(t) 192 | local iter = getiter(t) 193 | for k in iter(t) do 194 | t[k] = nil 195 | end 196 | return t 197 | end 198 | 199 | 200 | function lume.extend(t, ...) 201 | for i = 1, select("#", ...) do 202 | local x = select(i, ...) 203 | if x then 204 | for k, v in pairs(x) do 205 | t[k] = v 206 | end 207 | end 208 | end 209 | return t 210 | end 211 | 212 | 213 | function lume.shuffle(t) 214 | local rtn = {} 215 | for i = 1, #t do 216 | local r = math.random(i) 217 | if r ~= i then 218 | rtn[i] = rtn[r] 219 | end 220 | rtn[r] = t[i] 221 | end 222 | return rtn 223 | end 224 | 225 | 226 | function lume.sort(t, comp) 227 | local rtn = lume.clone(t) 228 | if comp then 229 | if type(comp) == "string" then 230 | table.sort(rtn, function(a, b) return a[comp] < b[comp] end) 231 | else 232 | table.sort(rtn, comp) 233 | end 234 | else 235 | table.sort(rtn) 236 | end 237 | return rtn 238 | end 239 | 240 | 241 | function lume.array(...) 242 | local t = {} 243 | for x in ... do t[#t + 1] = x end 244 | return t 245 | end 246 | 247 | 248 | function lume.each(t, fn, ...) 249 | local iter = getiter(t) 250 | if type(fn) == "string" then 251 | for _, v in iter(t) do v[fn](v, ...) end 252 | else 253 | for _, v in iter(t) do fn(v, ...) end 254 | end 255 | return t 256 | end 257 | 258 | 259 | function lume.map(t, fn) 260 | fn = iteratee(fn) 261 | local iter = getiter(t) 262 | local rtn = {} 263 | for k, v in iter(t) do rtn[k] = fn(v) end 264 | return rtn 265 | end 266 | 267 | 268 | function lume.all(t, fn) 269 | fn = iteratee(fn) 270 | local iter = getiter(t) 271 | for _, v in iter(t) do 272 | if not fn(v) then return false end 273 | end 274 | return true 275 | end 276 | 277 | 278 | function lume.any(t, fn) 279 | fn = iteratee(fn) 280 | local iter = getiter(t) 281 | for _, v in iter(t) do 282 | if fn(v) then return true end 283 | end 284 | return false 285 | end 286 | 287 | 288 | function lume.reduce(t, fn, first) 289 | local started = first ~= nil 290 | local acc = first 291 | local iter = getiter(t) 292 | for _, v in iter(t) do 293 | if started then 294 | acc = fn(acc, v) 295 | else 296 | acc = v 297 | started = true 298 | end 299 | end 300 | assert(started, "reduce of an empty table with no first value") 301 | return acc 302 | end 303 | 304 | 305 | function lume.unique(t) 306 | local rtn = {} 307 | for k in pairs(lume.invert(t)) do 308 | rtn[#rtn + 1] = k 309 | end 310 | return rtn 311 | end 312 | 313 | 314 | function lume.filter(t, fn, retainkeys) 315 | fn = iteratee(fn) 316 | local iter = getiter(t) 317 | local rtn = {} 318 | if retainkeys then 319 | for k, v in iter(t) do 320 | if fn(v) then rtn[k] = v end 321 | end 322 | else 323 | for _, v in iter(t) do 324 | if fn(v) then rtn[#rtn + 1] = v end 325 | end 326 | end 327 | return rtn 328 | end 329 | 330 | 331 | function lume.reject(t, fn, retainkeys) 332 | fn = iteratee(fn) 333 | local iter = getiter(t) 334 | local rtn = {} 335 | if retainkeys then 336 | for k, v in iter(t) do 337 | if not fn(v) then rtn[k] = v end 338 | end 339 | else 340 | for _, v in iter(t) do 341 | if not fn(v) then rtn[#rtn + 1] = v end 342 | end 343 | end 344 | return rtn 345 | end 346 | 347 | 348 | function lume.merge(...) 349 | local rtn = {} 350 | for i = 1, select("#", ...) do 351 | local t = select(i, ...) 352 | local iter = getiter(t) 353 | for k, v in iter(t) do 354 | rtn[k] = v 355 | end 356 | end 357 | return rtn 358 | end 359 | 360 | 361 | function lume.concat(...) 362 | local rtn = {} 363 | for i = 1, select("#", ...) do 364 | local t = select(i, ...) 365 | if t ~= nil then 366 | local iter = getiter(t) 367 | for _, v in iter(t) do 368 | rtn[#rtn + 1] = v 369 | end 370 | end 371 | end 372 | return rtn 373 | end 374 | 375 | 376 | function lume.find(t, value) 377 | local iter = getiter(t) 378 | for k, v in iter(t) do 379 | if v == value then return k end 380 | end 381 | return nil 382 | end 383 | 384 | 385 | function lume.match(t, fn) 386 | fn = iteratee(fn) 387 | local iter = getiter(t) 388 | for k, v in iter(t) do 389 | if fn(v) then return v, k end 390 | end 391 | return nil 392 | end 393 | 394 | 395 | function lume.count(t, fn) 396 | local count = 0 397 | local iter = getiter(t) 398 | if fn then 399 | fn = iteratee(fn) 400 | for _, v in iter(t) do 401 | if fn(v) then count = count + 1 end 402 | end 403 | else 404 | if lume.isarray(t) then 405 | return #t 406 | end 407 | for _ in iter(t) do count = count + 1 end 408 | end 409 | return count 410 | end 411 | 412 | 413 | function lume.slice(t, i, j) 414 | i = i and absindex(#t, i) or 1 415 | j = j and absindex(#t, j) or #t 416 | local rtn = {} 417 | for x = i < 1 and 1 or i, j > #t and #t or j do 418 | rtn[#rtn + 1] = t[x] 419 | end 420 | return rtn 421 | end 422 | 423 | 424 | function lume.first(t, n) 425 | if not n then return t[1] end 426 | return lume.slice(t, 1, n) 427 | end 428 | 429 | 430 | function lume.last(t, n) 431 | if not n then return t[#t] end 432 | return lume.slice(t, -n, -1) 433 | end 434 | 435 | 436 | function lume.invert(t) 437 | local rtn = {} 438 | for k, v in pairs(t) do rtn[v] = k end 439 | return rtn 440 | end 441 | 442 | 443 | function lume.pick(t, ...) 444 | local rtn = {} 445 | for i = 1, select("#", ...) do 446 | local k = select(i, ...) 447 | rtn[k] = t[k] 448 | end 449 | return rtn 450 | end 451 | 452 | 453 | function lume.keys(t) 454 | local rtn = {} 455 | local iter = getiter(t) 456 | for k in iter(t) do rtn[#rtn + 1] = k end 457 | return rtn 458 | end 459 | 460 | 461 | function lume.clone(t) 462 | local rtn = {} 463 | for k, v in pairs(t) do rtn[k] = v end 464 | return rtn 465 | end 466 | 467 | 468 | function lume.fn(fn, ...) 469 | assert(iscallable(fn), "expected a function as the first argument") 470 | local args = { ... } 471 | return function(...) 472 | local a = lume.concat(args, { ... }) 473 | return fn(unpack(a)) 474 | end 475 | end 476 | 477 | 478 | function lume.once(fn, ...) 479 | local f = lume.fn(fn, ...) 480 | local done = false 481 | return function(...) 482 | if done then return end 483 | done = true 484 | return f(...) 485 | end 486 | end 487 | 488 | 489 | local memoize_fnkey = {} 490 | local memoize_nil = {} 491 | 492 | function lume.memoize(fn) 493 | local cache = {} 494 | return function(...) 495 | local c = cache 496 | for i = 1, select("#", ...) do 497 | local a = select(i, ...) or memoize_nil 498 | c[a] = c[a] or {} 499 | c = c[a] 500 | end 501 | c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)} 502 | return unpack(c[memoize_fnkey]) 503 | end 504 | end 505 | 506 | 507 | function lume.combine(...) 508 | local n = select('#', ...) 509 | if n == 0 then return noop end 510 | if n == 1 then 511 | local fn = select(1, ...) 512 | if not fn then return noop end 513 | assert(iscallable(fn), "expected a function or nil") 514 | return fn 515 | end 516 | local funcs = {} 517 | for i = 1, n do 518 | local fn = select(i, ...) 519 | if fn ~= nil then 520 | assert(iscallable(fn), "expected a function or nil") 521 | funcs[#funcs + 1] = fn 522 | end 523 | end 524 | return function(...) 525 | for _, f in ipairs(funcs) do f(...) end 526 | end 527 | end 528 | 529 | 530 | function lume.call(fn, ...) 531 | if fn then 532 | return fn(...) 533 | end 534 | end 535 | 536 | 537 | function lume.time(fn, ...) 538 | local start = os.clock() 539 | local rtn = {fn(...)} 540 | return (os.clock() - start), unpack(rtn) 541 | end 542 | 543 | 544 | local lambda_cache = {} 545 | 546 | function lume.lambda(str) 547 | if not lambda_cache[str] then 548 | local args, body = str:match([[^([%w,_ ]-)%->(.-)$]]) 549 | assert(args and body, "bad string lambda") 550 | local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend" 551 | lambda_cache[str] = lume.dostring(s) 552 | end 553 | return lambda_cache[str] 554 | end 555 | 556 | 557 | local serialize 558 | 559 | local serialize_map = { 560 | [ "boolean" ] = tostring, 561 | [ "nil" ] = tostring, 562 | [ "string" ] = function(v) return string.format("%q", v) end, 563 | [ "number" ] = function(v) 564 | if v ~= v then return "0/0" -- nan 565 | elseif v == 1 / 0 then return "1/0" -- inf 566 | elseif v == -1 / 0 then return "-1/0" end -- -inf 567 | return tostring(v) 568 | end, 569 | [ "table" ] = function(t, stk) 570 | stk = stk or {} 571 | if stk[t] then error("circular reference") end 572 | local rtn = {} 573 | stk[t] = true 574 | for k, v in pairs(t) do 575 | rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk) 576 | end 577 | stk[t] = nil 578 | return "{" .. table.concat(rtn, ",") .. "}" 579 | end 580 | } 581 | 582 | setmetatable(serialize_map, { 583 | __index = function(_, k) error("unsupported serialize type: " .. k) end 584 | }) 585 | 586 | serialize = function(x, stk) 587 | return serialize_map[type(x)](x, stk) 588 | end 589 | 590 | function lume.serialize(x) 591 | return serialize(x) 592 | end 593 | 594 | 595 | function lume.deserialize(str) 596 | return lume.dostring("return " .. str) 597 | end 598 | 599 | 600 | function lume.split(str, sep) 601 | if not sep then 602 | return lume.array(str:gmatch("([%S]+)")) 603 | else 604 | assert(sep ~= "", "empty separator") 605 | local psep = patternescape(sep) 606 | return lume.array((str..sep):gmatch("(.-)("..psep..")")) 607 | end 608 | end 609 | 610 | 611 | function lume.trim(str, chars) 612 | if not chars then return str:match("^[%s]*(.-)[%s]*$") end 613 | chars = patternescape(chars) 614 | return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$") 615 | end 616 | 617 | 618 | function lume.wordwrap(str, limit) 619 | limit = limit or 72 620 | local check 621 | if type(limit) == "number" then 622 | check = function(s) return #s >= limit end 623 | else 624 | check = limit 625 | end 626 | local rtn = {} 627 | local line = "" 628 | for word, spaces in str:gmatch("(%S+)(%s*)") do 629 | local s = line .. word 630 | if check(s) then 631 | table.insert(rtn, line .. "\n") 632 | line = word 633 | else 634 | line = s 635 | end 636 | for c in spaces:gmatch(".") do 637 | if c == "\n" then 638 | table.insert(rtn, line .. "\n") 639 | line = "" 640 | else 641 | line = line .. c 642 | end 643 | end 644 | end 645 | table.insert(rtn, line) 646 | return table.concat(rtn) 647 | end 648 | 649 | 650 | function lume.format(str, vars) 651 | if not vars then return str end 652 | local f = function(x) 653 | return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}") 654 | end 655 | return (str:gsub("{(.-)}", f)) 656 | end 657 | 658 | 659 | function lume.trace(...) 660 | local info = debug.getinfo(2, "Sl") 661 | local t = { info.short_src .. ":" .. info.currentline .. ":" } 662 | for i = 1, select("#", ...) do 663 | local x = select(i, ...) 664 | if type(x) == "number" then 665 | x = string.format("%g", lume.round(x, .01)) 666 | end 667 | t[#t + 1] = tostring(x) 668 | end 669 | print(table.concat(t, " ")) 670 | end 671 | 672 | 673 | function lume.dostring(str) 674 | return assert((loadstring or load)(str))() 675 | end 676 | 677 | 678 | function lume.uuid() 679 | local fn = function(x) 680 | local r = math.random(16) - 1 681 | r = (x == "x") and (r + 1) or (r % 4) + 9 682 | return ("0123456789abcdef"):sub(r, r) 683 | end 684 | return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) 685 | end 686 | 687 | 688 | function lume.hotswap(modname) 689 | local oldglobal = lume.clone(_G) 690 | local updated = {} 691 | local function update(old, new) 692 | if updated[old] then return end 693 | updated[old] = true 694 | local oldmt, newmt = getmetatable(old), getmetatable(new) 695 | if oldmt and newmt then update(oldmt, newmt) end 696 | for k, v in pairs(new) do 697 | if type(v) == "table" then update(old[k], v) else old[k] = v end 698 | end 699 | end 700 | local err = nil 701 | local function onerror(e) 702 | for k in pairs(_G) do _G[k] = oldglobal[k] end 703 | err = lume.trim(e) 704 | end 705 | local ok, oldmod = pcall(require, modname) 706 | oldmod = ok and oldmod or nil 707 | xpcall(function() 708 | package.loaded[modname] = nil 709 | local newmod = require(modname) 710 | if type(oldmod) == "table" then update(oldmod, newmod) end 711 | for k, v in pairs(oldglobal) do 712 | if v ~= _G[k] and type(v) == "table" then 713 | update(v, _G[k]) 714 | _G[k] = v 715 | end 716 | end 717 | end, onerror) 718 | package.loaded[modname] = oldmod 719 | if err then return nil, err end 720 | return oldmod 721 | end 722 | 723 | 724 | local ripairs_iter = function(t, i) 725 | i = i - 1 726 | local v = t[i] 727 | if v ~= nil then 728 | return i, v 729 | end 730 | end 731 | 732 | function lume.ripairs(t) 733 | return ripairs_iter, t, (#t + 1) 734 | end 735 | 736 | 737 | function lume.color(str, mul) 738 | mul = mul or 1 739 | local r, g, b, a 740 | r, g, b = str:match("#(%x%x)(%x%x)(%x%x)") 741 | if r then 742 | r = tonumber(r, 16) / 0xff 743 | g = tonumber(g, 16) / 0xff 744 | b = tonumber(b, 16) / 0xff 745 | a = 1 746 | elseif str:match("rgba?%s*%([%d%s%.,]+%)") then 747 | local f = str:gmatch("[%d.]+") 748 | r = (f() or 0) / 0xff 749 | g = (f() or 0) / 0xff 750 | b = (f() or 0) / 0xff 751 | a = f() or 1 752 | else 753 | error(("bad color string '%s'"):format(str)) 754 | end 755 | return r * mul, g * mul, b * mul, a * mul 756 | end 757 | 758 | 759 | local chain_mt = {} 760 | chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), 761 | function(fn) 762 | return function(self, ...) 763 | self._value = fn(self._value, ...) 764 | return self 765 | end 766 | end) 767 | chain_mt.__index.result = function(x) return x._value end 768 | 769 | function lume.chain(value) 770 | return setmetatable({ _value = value }, chain_mt) 771 | end 772 | 773 | setmetatable(lume, { 774 | __call = function(_, ...) 775 | return lume.chain(...) 776 | end 777 | }) 778 | 779 | 780 | return lume 781 | -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | local tester = require "util.tester" 2 | 3 | package.path = "../?.lua;" .. package.path 4 | 5 | local lume = require "lume" 6 | 7 | local tests = {} 8 | local testeq = tester.test.equal 9 | 10 | -- lume.clamp 11 | tests["lume.clamp"] = function() 12 | testeq( lume.clamp(8, 5, 10), 8 ) 13 | testeq( lume.clamp(12, 5, 10), 10 ) 14 | testeq( lume.clamp(-1, 5, 10), 5 ) 15 | testeq( lume.clamp(-1, -10, 10), -1 ) 16 | testeq( lume.clamp(-100, -10, 10), -10 ) 17 | testeq( lume.clamp(13, 8, 8), 8 ) 18 | testeq( lume.clamp(3, 8, 8), 8 ) 19 | end 20 | 21 | -- lume.round 22 | tests["lume.round"] = function() 23 | testeq( lume.round(.5), 1 ) 24 | testeq( lume.round(-.5), -1 ) 25 | testeq( lume.round(2.4), 2 ) 26 | testeq( lume.round(123, 10), 120 ) 27 | testeq( lume.round(129, 64), 128 ) 28 | testeq( lume.round(-123.45, .1), -123.5 ) 29 | testeq( lume.round(0), 0 ) 30 | end 31 | 32 | -- lume.sign 33 | tests["lume.sign"] = function() 34 | testeq( lume.sign(-10), -1 ) 35 | testeq( lume.sign(10), 1 ) 36 | testeq( lume.sign(0), 1 ) 37 | end 38 | 39 | -- lume.lerp 40 | tests["lume.lerp"] = function() 41 | testeq( lume.lerp(100, 200, .5), 150 ) 42 | testeq( lume.lerp(100, 200, .25), 125 ) 43 | testeq( lume.lerp(100, 200, 2), 200 ) 44 | testeq( lume.lerp(100, 200, -2), 100 ) 45 | end 46 | 47 | -- lume.smooth 48 | tests["lume.smooth"] = function() 49 | testeq( lume.smooth(100, 200, .5), 150 ) 50 | testeq( lume.smooth(100, 200, 0), 100 ) 51 | testeq( lume.smooth(100, 200, 1), 200 ) 52 | testeq( lume.smooth(100, 200, 2), 200 ) 53 | testeq( lume.smooth(100, 200, -2), 100 ) 54 | end 55 | 56 | -- lume.pingpong 57 | tests["lume.pingpong"] = function() 58 | testeq( lume.pingpong(0), 0 ) 59 | testeq( lume.pingpong(1.5), .5 ) 60 | testeq( lume.pingpong(-.2), .2 ) 61 | testeq( lume.pingpong(-1.6), .4 ) 62 | testeq( lume.pingpong(1.8), .2 ) 63 | end 64 | 65 | -- lume.distance 66 | tests["lume.distance"] = function() 67 | testeq( lume.distance(15, 20, 15, 20), 0 ) 68 | testeq( lume.distance(13, 44, 156, 232), 236.205419074 ) 69 | testeq( lume.distance(-23, 66, -232, 123), 216.633330769 ) 70 | local x = lume.distance(13, 15, -2, 81) 71 | testeq( lume.distance(13, 15, -2, 81, true), x * x ) 72 | end 73 | 74 | -- lume.angle 75 | tests["lume.angle"] = function() 76 | testeq( lume.angle(10, 10, 10, 10), math.rad(0) ) 77 | testeq( lume.angle(10, 10, 20, 10), math.rad(0) ) 78 | testeq( lume.angle(10, 10, 5, 10), math.rad(180) ) 79 | testeq( lume.angle(10, 10, 20, 20), math.rad(45) ) 80 | testeq( lume.angle(10, 10, 10, 30), math.rad(90) ) 81 | end 82 | 83 | -- lume.vector 84 | tests["lume.vector"] = function() 85 | local function cmp(a, b) return math.abs(a - b) < 10e-6 end 86 | local x, y 87 | x, y = lume.vector(0, 10) 88 | testeq( cmp(x, 10) and cmp(y, 0), true ) 89 | x, y = lume.vector(math.pi, 100) 90 | testeq( cmp(x, -100) and cmp(y, 0), true ) 91 | x, y = lume.vector(math.pi * 0.25, 100) 92 | testeq( cmp(x, 70.71067811865476) and cmp(y, 70.71067811865476), true ) 93 | end 94 | 95 | -- lume.random 96 | tests["lume.random"] = function() 97 | testeq( type(lume.random()), "number" ) 98 | testeq( type(lume.random(1)), "number" ) 99 | testeq( type(lume.random(1, 2)), "number" ) 100 | end 101 | 102 | -- lume.randomchoice 103 | tests["lume.randomchoice"] = function() 104 | local t = {} 105 | for i = 0, 1000 do 106 | t[lume.randomchoice({"a", "b", "c", "d"})] = true 107 | end 108 | testeq( t.a and t.b and t.c and t.d, true ) 109 | testeq( lume.randomchoice({true}), true ) 110 | end 111 | 112 | -- lume.weightedchoice 113 | tests["lume.weightedchoice"] = function() 114 | testeq( lume.weightedchoice( {a = 1} ), "a" ) 115 | testeq( lume.weightedchoice( {a = 0, b = 1} ), "b" ) 116 | tester.test.error( lume.weightedchoice, {} ) 117 | tester.test.error( lume.weightedchoice, { a = 0, b = 0 } ) 118 | tester.test.error( lume.weightedchoice, { a = 1, b = -1 } ) 119 | end 120 | 121 | -- lume.push 122 | tests["lume.push"] = function() 123 | local t = { 1, 2 } 124 | lume.push(t, 3, 4) 125 | testeq(t, { 1, 2, 3, 4 }) 126 | lume.push(t, 5, nil, 6, nil, 7) 127 | testeq(t, { 1, 2, 3, 4, 5, 6, 7 }) 128 | lume.push(t) 129 | testeq(t, { 1, 2, 3, 4, 5, 6, 7 }) 130 | local x, y = lume.push(t, 123, 456) 131 | testeq(x, 123) 132 | testeq(y, 456) 133 | end 134 | 135 | -- lume.remove 136 | tests["lume.remove"] = function() 137 | local t = { 1, 2, 3, 4, 5 } 138 | lume.remove(t, 3) 139 | testeq(t, { 1, 2, 4, 5 }) 140 | lume.remove(t, 1) 141 | testeq(t, { 2, 4, 5 }) 142 | lume.remove(t, 5) 143 | testeq(t, { 2, 4 }) 144 | local x = lume.remove(t, 123) 145 | testeq(x, 123) 146 | end 147 | 148 | -- lume.clear 149 | tests["lume.clear"] = function() 150 | local t = { 1, 2, 3 } 151 | lume.clear(t) 152 | testeq(t, {}) 153 | local m = { a = 1, b = 2, c = 3 } 154 | lume.clear(m) 155 | testeq(m, {}) 156 | testeq( lume.clear(t) == t, true ) 157 | end 158 | 159 | -- lume.extend 160 | tests["lume.extend"] = function() 161 | local t = { a = 10, b = 20, c = 30 } 162 | testeq( lume.extend(t) == t, true ) 163 | lume.extend(t, { d = 40 }, { e = 50 }) 164 | testeq( t, { a = 10, b = 20, c = 30, d = 40, e = 50 } ) 165 | lume.extend(t, { a = "cat", b = "dog" }, { b = "owl", c = "fox" }) 166 | testeq( t, { a = "cat", b = "owl", c = "fox", d = 40, e = 50 } ) 167 | end 168 | 169 | -- lume.shuffle 170 | tests["lume.shuffle"] = function() 171 | local t = {1, 2, 3, 4, 5} 172 | t = lume.shuffle(t) 173 | table.sort(t) 174 | testeq( t, {1, 2, 3, 4, 5} ) 175 | testeq( lume.shuffle({}), {} ) 176 | end 177 | 178 | -- lume.sort 179 | tests["lume.sort"] = function() 180 | local t = { 1, 5, 2, 4, 3 } 181 | local fn = function(a, b) return a > b end 182 | testeq( t == lume.sort(t), false ) 183 | testeq( lume.sort(t), { 1, 2, 3, 4, 5 } ) 184 | testeq( lume.sort(t, fn), { 5, 4, 3, 2, 1 } ) 185 | testeq( t, { 1, 5, 2, 4, 3 } ) 186 | local t = { { id = 2 }, { id = 3 }, { id = 1 } } 187 | testeq( lume.sort(t, "id"), { { id = 1 }, { id = 2 }, { id = 3 } }) 188 | end 189 | 190 | -- lume.array 191 | tests["lume.array"] = function() 192 | local t = lume.array(pairs({a=0, b=0, c=0})) 193 | table.sort(t) 194 | testeq( t, {"a", "b", "c"} ) 195 | testeq( lume.array(ipairs({0, 0, 0})), {1, 2, 3} ) 196 | end 197 | 198 | -- lume.each 199 | tests["lume.each"] = function() 200 | local acc = 1 201 | lume.each({1, 2, 3}, function(x) acc = acc + x end) 202 | testeq( acc, 7 ) 203 | 204 | local acc = 1 205 | local f = function(o, x) acc = acc + x end 206 | local f2 = function() end 207 | local t = {a = {f = f}, b = {f = f}, c = {f = f2}} 208 | lume.each(t, "f", 10) 209 | testeq( acc, 21 ) 210 | end 211 | 212 | -- lume.map 213 | tests["lume.map"] = function() 214 | testeq( lume.map({1, 2, 3}, function(x) return x * 2 end), {2, 4, 6} ) 215 | testeq( lume.map({a=2,b=3}, function(x) return x * 2 end), {a=4,b=6} ) 216 | local t = {{ id = 10 }, { id = 20 }, { id = 30 }} 217 | testeq( lume.map(t, "id"), { 10, 20, 30 }) 218 | end 219 | 220 | -- lume.all 221 | tests["lume.all"] = function() 222 | testeq( lume.all({true, true, false, true}), false ) 223 | testeq( lume.all({true, true, true, true}), true ) 224 | testeq( lume.all({2, 3, 4, 5}, function(x) return x % 2 == 0 end), false ) 225 | testeq( lume.all({2, 4, 6, 8}, function(x) return x % 2 == 0 end), true ) 226 | testeq( lume.all({{ x = 1 }, {}, { x = 3 }}, "x"), false ) 227 | testeq( lume.all({{ x = 1 }, { x = 2 }, { x = 3 }}, "x"), true ) 228 | testeq( lume.all({{ x = 1 }, { x = 2 }, { x = 3 }}, { x = 2 }), false ) 229 | testeq( lume.all({{ x = 2 }, { x = 2 }, { x = 2 }}, { x = 2 }), true ) 230 | end 231 | 232 | -- lume.any 233 | tests["lume.any"] = function() 234 | testeq( lume.any({true, true, false, true}), true ) 235 | testeq( lume.any({false, false, false}), false ) 236 | testeq( lume.any({2, 3, 4, 5}, function(x) return x % 2 == 0 end), true ) 237 | testeq( lume.any({1, 3, 5, 7}, function(x) return x % 2 == 0 end), false ) 238 | local t = {{ id = 10 }, { id = 20 }, { id = 30 }} 239 | testeq( lume.any(t, { id = 10 }), true ) 240 | testeq( lume.any(t, { id = 40 }), false ) 241 | end 242 | 243 | -- lume.reduce 244 | tests["lume.reduce"] = function() 245 | local concat = function(a, b) return a .. b end 246 | local add = function(a, b) return a + b end 247 | local any = function(a, b) return a or b end 248 | testeq( lume.reduce({"cat", "dog"}, concat, ""), "catdog" ) 249 | testeq( lume.reduce({"cat", "dog"}, concat, "pig"), "pigcatdog" ) 250 | testeq( lume.reduce({"me", "ow"}, concat), "meow" ) 251 | testeq( lume.reduce({1, 2, 3, 4}, add), 10 ) 252 | testeq( lume.reduce({1, 2, 3, 4}, add, 5), 15 ) 253 | testeq( lume.reduce({1}, add), 1 ) 254 | testeq( lume.reduce({}, concat, "potato"), "potato" ) 255 | testeq( lume.reduce({a=1, b=2}, add, 5), 8 ) 256 | testeq( lume.reduce({a=1, b=2}, add), 3 ) 257 | testeq( lume.reduce({false, false, false}, any), false ) 258 | testeq( lume.reduce({false, true, false}, any), true ) 259 | tester.test.error(lume.reduce, {}, add) 260 | end 261 | 262 | -- lume.unique 263 | tests["lume.unique"] = function() 264 | testeq( lume.unique({}), {} ) 265 | local t = lume.unique({1, 2, 3, 2, 5, 6, 6}) 266 | table.sort(t) 267 | testeq( t, {1, 2, 3, 5, 6} ) 268 | local t = lume.unique({"a", "b", "c", "b", "d"}) 269 | table.sort(t) 270 | testeq( t, {"a", "b", "c", "d"} ) 271 | end 272 | 273 | -- lume.filter 274 | tests["lume.filter"] = function() 275 | local t = lume.filter({1, 2, 3, 4, 5}, function(x) return x % 2 == 0 end ) 276 | testeq( t, {2, 4} ) 277 | local t = lume.filter({a=1, b=2, c=3}, function(x) return x == 2 end, true) 278 | testeq( t, {b=2} ) 279 | local t = lume.filter({{ x=1, y=1 }, { x=2, y=2 }, { x=1, y=3 }}, { x = 1 }) 280 | testeq( t, {{ x=1, y=1 }, {x=1, y=3}} ) 281 | end 282 | 283 | -- lume.reject 284 | tests["lume.reject"] = function() 285 | local t = lume.reject({1, 2, 3, 4, 5}, function(x) return x % 2 == 0 end ) 286 | testeq( t, {1, 3, 5} ) 287 | local t = lume.reject({a=1, b=2, c=3}, function(x) return x == 2 end, true) 288 | testeq( t, {a=1, c=3} ) 289 | local t = lume.reject({{ x=1, y=1 }, { x=2, y=2 }, { x=1, y=3 }}, { x = 1 }) 290 | testeq( t, {{ x=2, y=2 }} ) 291 | end 292 | 293 | -- lume.merge 294 | tests["lume.merge"] = function() 295 | testeq( lume.merge(), {} ) 296 | testeq( lume.merge({x=1, y=2}), {x=1, y=2} ) 297 | testeq( lume.merge({a=1, b=2}, {b=3, c=4}), {a=1, b=3, c=4} ) 298 | end 299 | 300 | -- lume.concat 301 | tests["lume.concat"] = function() 302 | testeq( lume.concat(nil), {} ) 303 | testeq( lume.concat({1, 2, 3}), {1, 2, 3} ) 304 | testeq( lume.concat({1, 2, 3}, {4, 5, 6}), {1, 2, 3, 4, 5, 6} ) 305 | testeq( lume.concat({1, 2, 3}, {4, 5, 6}, nil, {7}), {1, 2, 3, 4, 5, 6, 7} ) 306 | end 307 | 308 | -- lume.find 309 | tests["lume.find"] = function() 310 | testeq( lume.find({"a", "b", "c"}, "b"), 2 ) 311 | testeq( lume.find({"a", "b", "c"}, "c"), 3 ) 312 | testeq( lume.find({a=1, b=5, c=7}, 5), "b" ) 313 | end 314 | 315 | -- lume.match 316 | tests["lume.match"] = function() 317 | local t = { "a", "b", "c", "d" } 318 | local t2 = { a = 1, b = 2, c = 3, d = 4 } 319 | local t3 = { {x=1, y=2}, {x=3, y=4}, {x=5, y=6} } 320 | local v, k = lume.match(t, function(x) return x > "c" end) 321 | testeq( v, "d" ) 322 | testeq( k, 4 ) 323 | local v, k = lume.match(t, function(x) return x < "b" end) 324 | testeq( v, "a" ) 325 | testeq( k, 1 ) 326 | local v, k = lume.match(t2, function(x) return x < 2 end) 327 | testeq( v, 1 ) 328 | testeq( k, "a" ) 329 | local v, k = lume.match(t2, function(x) return x > 5 end) 330 | testeq( v, nil ) 331 | testeq( k, nil ) 332 | local v, k = lume.match(t3, { x = 3, y = 4 }) 333 | testeq( k, 2 ) 334 | end 335 | 336 | -- lume.count 337 | tests["lume.count"] = function() 338 | local t = { a = 1, b = 2, c = 5, [13] = 22, z = 8 } 339 | testeq( lume.count(t), 5 ) 340 | testeq( lume.count(t, function(x) return x % 2 == 0 end ), 3 ) 341 | local a = { 5, 6, 7, 8, 9 } 342 | testeq( lume.count(a), #a ) 343 | local t = { { n = 20 }, { n = 30 }, { n = 40 }, { n = 20 } } 344 | testeq( lume.count(t, { n = 20 }), 2 ) 345 | testeq( lume.count(t, { n = 30 }), 1 ) 346 | testeq( lume.count(t, { n = 50 }), 0 ) 347 | end 348 | 349 | -- lume.slice 350 | tests["lume.slice"] = function() 351 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 4), {"b", "c", "d"} ) 352 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, -2), {"b", "c", "d"} ) 353 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 3, -1), {"c", "d", "e"} ) 354 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 3), {"c", "d", "e"} ) 355 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 4), {"d", "e"} ) 356 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 1, 1), {"a"} ) 357 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 2, 1), {} ) 358 | testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, -2), {"c", "d"} ) 359 | testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, 1), {} ) 360 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 0, 1), {"a"} ) 361 | testeq( lume.slice({"a", "b", "c", "d", "e"}, 0, 0), {} ) 362 | testeq( lume.slice({"a", "b", "c", "d", "e"}, -3), {"c", "d", "e"} ) 363 | testeq( lume.slice({"a", "b", "c", "d", "e"}, -3, 900), {"c", "d", "e"} ) 364 | end 365 | 366 | -- lume.first 367 | tests["lume.first"] = function() 368 | local t = { "a", "b", "c", "d", "e" } 369 | testeq( lume.first(t), "a" ) 370 | testeq( lume.first(t, 1), { "a" } ) 371 | testeq( lume.first(t, 2), { "a", "b" } ) 372 | end 373 | 374 | -- lume.last 375 | tests["lume.last"] = function() 376 | local t = { "a", "b", "c", "d", "e" } 377 | testeq( lume.last(t), "e" ) 378 | testeq( lume.last(t, 1), { "e" } ) 379 | testeq( lume.last(t, 2), { "d", "e" } ) 380 | end 381 | 382 | -- lume.invert 383 | tests["lume.invert"] = function() 384 | testeq( lume.invert({}), {} ) 385 | testeq( lume.invert{a = "x", b = "y"}, {x = "a", y = "b"} ) 386 | testeq( lume.invert{a = 1, b = 2}, {"a", "b"} ) 387 | testeq( lume.invert(lume.invert{a = 1, b = 2}), {a = 1, b = 2} ) 388 | end 389 | 390 | -- lume.pick 391 | tests["lume.pick"] = function() 392 | local t = { cat = 10, dog = 20, fox = 30, owl = 40 } 393 | testeq( lume.pick(t, "cat", "dog"), { cat = 10, dog = 20 } ) 394 | testeq( lume.pick(t, "fox", "owl"), { fox = 30, owl = 40 } ) 395 | testeq( lume.pick(t, "owl"), { owl = 40 } ) 396 | testeq( lume.pick(t), {} ) 397 | end 398 | 399 | -- lume.keys 400 | tests["lume.keys"] = function() 401 | testeq( lume.keys({}), {} ) 402 | local t = lume.keys({ aaa = 1, bbb = 2, ccc = 3 }) 403 | table.sort(t) 404 | testeq( t, {"aaa", "bbb", "ccc"} ) 405 | local t = lume.keys({ "x", "x", "x" }) 406 | testeq( t, {1, 2, 3} ) 407 | end 408 | 409 | -- lume.clone 410 | tests["lume.clone"] = function() 411 | local t = {6, 7, 4, 5} 412 | testeq( lume.clone(t) ~= t, true ) 413 | testeq( lume.clone(t), {6, 7, 4, 5} ) 414 | testeq( lume.clone({x=2, y="a"}), {x=2, y="a"} ) 415 | end 416 | 417 | -- lume.fn 418 | tests["lume.fn"] = function() 419 | local f = lume.fn(function(a, b) return a + b end, 10) 420 | testeq( f(5), 15 ) 421 | tester.test.error( lume.fn, 123 ) 422 | end 423 | 424 | -- lume.once 425 | tests["lume.once"] = function() 426 | local f = lume.once(function(a, b) return a + b end, 10) 427 | testeq( f(5), 15 ) 428 | testeq( f(5), nil ) 429 | tester.test.error( lume.once, 123 ) 430 | end 431 | 432 | -- lume.memoize 433 | tests["lume.memoize"] = function() 434 | local f = lume.memoize( 435 | function(a, b, c) 436 | return tostring(a) .. tostring(b) .. tostring(c) 437 | end) 438 | testeq( f("hello", nil, 15), "hellonil15" ) 439 | testeq( f("hello", nil, 15), "hellonil15" ) 440 | testeq( f(), "nilnilnil" ) 441 | testeq( f(), "nilnilnil" ) 442 | local f2 = lume.memoize(function() end) 443 | testeq( f2(), nil ) 444 | end 445 | 446 | -- lume.combine 447 | tests["lume.combine"] = function() 448 | local acc = 0 449 | local a = function(x, y) acc = acc + x + y end 450 | local b = function(x, y) acc = acc + x * y end 451 | local fn = lume.combine(a, b) 452 | fn(10, 20) 453 | testeq( acc, 230 ) 454 | acc = 0 455 | fn = lume.combine(nil, a, nil, b, nil) 456 | fn(10, 20) 457 | testeq( acc, 230 ) 458 | local x = false 459 | fn = lume.combine(function() x = true end) 460 | fn() 461 | testeq( x, true ) 462 | testeq( type(lume.combine(nil)), "function" ) 463 | testeq( type(lume.combine()), "function" ) 464 | end 465 | 466 | -- lume.call 467 | tests["lume.call"] = function() 468 | local add = function(a, b) return a + b end 469 | testeq( lume.call(), nil ) 470 | testeq( lume.call(nil, 1, 2, 3), nil ) 471 | testeq( lume.call(add, 1, 2), 3 ) 472 | end 473 | 474 | -- lume.time 475 | tests["lume.time"] = function() 476 | local t, a, b, c = lume.time(function(x) return 50, 60, x end, 70) 477 | testeq( type(t), "number" ) 478 | testeq( {a, b, c}, {50, 60, 70} ) 479 | end 480 | 481 | -- lume.lambda 482 | tests["lume.lambda"] = function() 483 | testeq( lume.lambda "x->x*x"(10), 100 ) 484 | testeq( lume.lambda "x->x*x"(20), 400 ) 485 | testeq( lume.lambda "x,y -> 2*x+y"(10,5), 25 ) 486 | testeq( lume.lambda "a, b -> a / b"(1, 2), .5 ) 487 | testeq( lume.lambda "a -> 'hi->' .. a"("doggy"), "hi->doggy" ) 488 | testeq( lume.lambda "A1,_->A1.._"("te","st"), "test" ) 489 | testeq( lume.lambda "->"(1,2,3), nil ) 490 | tester.test.error( lume.lambda, "abc" ) 491 | tester.test.error( lume.lambda, "" ) 492 | tester.test.error( lume.lambda, "a,b->a->b" ) 493 | tester.test.error( lume.lambda, "(a),b->a+b" ) 494 | end 495 | 496 | -- lume.serialize / lume.deserialize 497 | tests["lume.serialize, lume.deserialize"] = function() 498 | local t = { 1, 2, 3, 4, true, false, "cat", "dog", {1, 2, 3} } 499 | local s = lume.serialize(t) 500 | testeq( lume.deserialize(s), t ) 501 | testeq( lume.deserialize(lume.serialize(math.huge)), math.huge ) 502 | testeq( lume.deserialize(lume.serialize(-math.huge)), -math.huge ) 503 | local x = lume.deserialize(lume.serialize(0 / 0)) -- nan 504 | testeq( x ~= x, true ) 505 | end 506 | 507 | -- lume.split 508 | tests["lume.split"] = function() 509 | testeq( lume.split("cat dog pig"), {"cat", "dog", "pig"} ) 510 | testeq( lume.split("cat,dog,pig", ","), {"cat", "dog", "pig"} ) 511 | testeq( lume.split("cat,dog;pig", ";"), {"cat,dog", "pig"} ) 512 | testeq( lume.split("cat,dog,,pig", ","), {"cat", "dog", "", "pig"} ) 513 | testeq( lume.split(";;;cat;", ";"), {"", "", "", "cat", ""} ) 514 | testeq( lume.split("cat.dog", "."), {"cat", "dog"} ) 515 | testeq( lume.split("cat%dog", "%"), {"cat", "dog"} ) 516 | testeq( lume.split("1<>2<>3", "<>"), {"1", "2", "3"} ) 517 | tester.test.error( lume.split, "abc", "" ) 518 | end 519 | 520 | -- lume.trim 521 | tests["lume.trim"] = function() 522 | testeq( lume.trim(" hello world "), "hello world" ) 523 | testeq( lume.trim("-=-hello-world===", "-="), "hello-world" ) 524 | testeq( lume.trim("***hello world*-*", "*"), "hello world*-" ) 525 | testeq( lume.trim("...hello world.", "."), "hello world" ) 526 | testeq( lume.trim("^.hello world]^", "^.]"), "hello world" ) 527 | end 528 | 529 | -- lume.wordwrap 530 | tests["lume.wordwrap"] = function() 531 | local str = "A small string with some words and then some more words" 532 | local b = "A small string with \nsome words and then \nsome more words" 533 | local fn = function(str) return #str >= 20 end 534 | testeq( lume.wordwrap(str), str ) 535 | testeq( lume.wordwrap(str, 20), b ) 536 | testeq( lume.wordwrap(str, fn), b ) 537 | end 538 | 539 | -- lume.format 540 | tests["lume.format"] = function() 541 | local str = lume.format("a {a} in a {b}", {a = "mouse", b = "house"}) 542 | testeq( str, "a mouse in a house" ) 543 | testeq( lume.format("number {num}", {num = 13}), "number 13" ) 544 | testeq( lume.format("{missing} {keys}", {}), "{missing} {keys}" ) 545 | testeq( lume.format("A {missing} table"), "A {missing} table" ) 546 | testeq( lume.format("{1} idx {2}", {"an", "test"}), "an idx test" ) 547 | testeq( lume.format("bad idx {-1}", {"x"}), "bad idx {-1}" ) 548 | testeq( lume.format("empty {}", {"idx"}), "empty {}" ) 549 | end 550 | 551 | -- lume.trace 552 | tests["lume.trace"] = function() 553 | local oldprint = print 554 | local file, line, msg 555 | print = function(x) 556 | file, line, msg = x:match("(.-):(.-): (.*)") 557 | end 558 | lume.trace("Hi world", 123.456, 1, nil) 559 | print = oldprint 560 | testeq( file:match(".lua$"), ".lua" ) 561 | testeq( tonumber(line) ~= nil, true ) 562 | testeq( msg, "Hi world 123.46 1 nil" ) 563 | end 564 | 565 | -- lume.dostring 566 | tests["lume.dostring"] = function() 567 | testeq( lume.dostring([[return "hello!"]]), "hello!" ) 568 | testeq( lume.dostring([[return 12345]]), 12345 ) 569 | end 570 | 571 | -- lume.uuid 572 | tests["lume.uuid"] = function() 573 | testeq( type(lume.uuid()), "string" ) 574 | testeq( #lume.uuid(), 36 ) 575 | end 576 | 577 | -- lume.hotswap 578 | tests["lume.hotswap"] = function() 579 | local ok, err = lume.hotswap("bad_module_name") 580 | testeq( ok, nil ) 581 | testeq( type(err), "string" ) 582 | end 583 | 584 | -- lume.ripairs 585 | tests["lume.ripairs"] = function() 586 | local t = { "a", "b", false, "c" } 587 | local r = {} 588 | for i, v in lume.ripairs(t) do 589 | table.insert(r, { i, v }) 590 | end 591 | testeq( r, { { 4, "c" }, { 3, false }, { 2, "b" }, { 1, "a" } }) 592 | tester.test.error(lume.ripairs, nil) 593 | end 594 | 595 | -- lume.color 596 | tests["lume.color"] = function() 597 | testeq({ lume.color("#ff0000") }, { 1, 0, 0, 1 } ) 598 | testeq({ lume.color("#00ff00") }, { 0, 1, 0, 1 } ) 599 | testeq({ lume.color("#0000ff") }, { 0, 0, 1, 1 } ) 600 | testeq({ lume.color("rgb( 255, 255, 255 )") }, { 1, 1, 1, 1 } ) 601 | testeq({ lume.color("rgb (0, 0, 0)") }, { 0, 0, 0, 1 } ) 602 | testeq({ lume.color("rgba(255, 255, 255, .5)") }, { 1, 1, 1, .5 } ) 603 | testeq({ lume.color("#ffffff", 2) }, { 2, 2, 2, 2 } ) 604 | testeq({ lume.color("rgba(255, 255, 255, 1)", 3) }, { 3, 3, 3, 3 } ) 605 | tester.test.error(lume.color, "#ff00f") 606 | tester.test.error(lume.color, "#xyzxyz") 607 | tester.test.error(lume.color, "rgba(hello)") 608 | tester.test.error(lume.color, "rgba()") 609 | tester.test.error(lume.color, "rgba(1, 1, 1, 1") 610 | end 611 | 612 | -- lume.chain 613 | tests["lume.chain"] = function() 614 | local t = lume.chain({1, 2}):map(function(x) return x * 2 end):result() 615 | testeq( t, { 2, 4 } ) 616 | testeq( lume.chain(10):result(), 10 ) 617 | local t = lume({1, 2}):map(function(x) return x * 2 end):result() 618 | testeq( t, { 2, 4 } ) 619 | end 620 | 621 | 622 | tester.dotests(tests) 623 | tester.test.global() 624 | tester.printresults() 625 | -------------------------------------------------------------------------------- /test/util/tester.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local tester = { 4 | test = {}, 5 | linecache = {}, 6 | globals = {}, 7 | passcount = 0, 8 | failcount = 0 9 | } 10 | 11 | 12 | local function isequal(a, b) 13 | if type(a) ~= type(b) then return nil end 14 | local t = {} 15 | function t.table(a, b) 16 | for k, v in pairs(a) do if not isequal(b[k], v) then return nil end end 17 | for k, v in pairs(b) do if not isequal(a[k], v) then return nil end end 18 | return true 19 | end 20 | function t.number(a, b) return math.abs(a - b) < 10e-9 end 21 | return t[type(a)] and t[type(a)](a, b) or (a == b) 22 | end 23 | 24 | 25 | local function stringify(x) 26 | if type(x) == "number" then return string.format("%.2f", x) end 27 | return string.format("%q", tostring(x)) 28 | end 29 | 30 | 31 | local function getline(file, line) 32 | if not tester.linecache[file] then 33 | local t = {} 34 | for line in io.lines(file) do 35 | t[#t + 1] = line 36 | end 37 | tester.linecache[file] = t 38 | end 39 | return tester.linecache[file][line] 40 | end 41 | 42 | 43 | local function truncate(str, max) 44 | max = max or 72 45 | if #str > max then 46 | return str:sub(1, max - 3) .. "..." 47 | end 48 | return str 49 | end 50 | 51 | 52 | local function has(t, value) 53 | for k, v in pairs(t) do 54 | if v == value then return true end 55 | end 56 | return false 57 | end 58 | 59 | 60 | local function makelogstr(passed, file, line) 61 | local t = {} 62 | t[#t + 1] = passed and "[\27[32mPASS\27[0m]" or "[\27[31mFAIL\27[0m]" 63 | t[#t + 1] = file .. ":" .. line .. ":" 64 | t[#t + 1] = getline(file, line) :gsub(" %s+", " ") :gsub("^ *", "") 65 | return truncate(table.concat(t, " ")) 66 | end 67 | 68 | 69 | local function dopass(file, line) 70 | print(makelogstr(true, file, line)) 71 | tester.passcount = tester.passcount + 1 72 | end 73 | 74 | 75 | local function dofail(file, line) 76 | print(makelogstr(false, file, line)) 77 | tester.failcount = tester.failcount + 1 78 | end 79 | 80 | 81 | local function printfailmsg(str) 82 | print(string.rep(" ", 7) .. str) 83 | end 84 | 85 | 86 | 87 | 88 | function tester.init() 89 | for k, v in pairs(_G) do 90 | tester.globals[k] = v 91 | end 92 | return tester 93 | end 94 | 95 | 96 | function tester.test.global(expectedglobals) 97 | expectedglobals = expectedglobals or {} 98 | local info = debug.getinfo(2) 99 | local unexpected = {} 100 | for k in pairs(_G) do 101 | if not tester.globals[k] and not has(expectedglobals, k) then 102 | table.insert(unexpected, "Unexpected global '" .. k .. "'") 103 | end 104 | end 105 | if #unexpected == 0 then 106 | dopass(info.short_src, info.currentline) 107 | else 108 | dofail(info.short_src, info.currentline) 109 | for _, v in pairs(unexpected) do printfailmsg(v) end 110 | end 111 | end 112 | 113 | 114 | function tester.test.equal(result, expected) 115 | local passed = isequal(result, expected) 116 | local info = debug.getinfo(2) 117 | if passed then 118 | dopass(info.short_src, info.currentline) 119 | else 120 | dofail(info.short_src, info.currentline) 121 | if type(expected) == "table" and type(result) == "table" then 122 | printfailmsg("Tables do not match") 123 | else 124 | printfailmsg(string.format("Expected %s got %s", 125 | stringify(expected), stringify(result) )) 126 | end 127 | end 128 | end 129 | 130 | 131 | function tester.test.error(fn, ...) 132 | local passed = not pcall(fn, ...) 133 | local info = debug.getinfo(2) 134 | if passed then 135 | dopass(info.short_src, info.currentline) 136 | else 137 | dofail(info.short_src, info.currentline) 138 | printfailmsg("Expected an error to be raised") 139 | end 140 | end 141 | 142 | 143 | function tester.dotests(t) 144 | local keys = {} 145 | for k in pairs(t) do table.insert(keys, k) end 146 | table.sort(keys) 147 | for _, k in pairs(keys) do 148 | print("\27[33m-- " .. k .. "\27[0m") 149 | t[k]() 150 | end 151 | end 152 | 153 | 154 | function tester.printresults() 155 | local str = table.concat{ 156 | "-- ", 157 | string.format("Results: %d Total", tester.passcount + tester.failcount), 158 | " ", string.format("%d Passed", tester.passcount), 159 | " ", string.format("%d Failed", tester.failcount), 160 | " --", } 161 | local b = string.rep("-", #str) 162 | print(table.concat{b, "\n", str, "\n", b}) 163 | end 164 | 165 | 166 | return tester.init() 167 | --------------------------------------------------------------------------------