add gs
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
--[[
|
||||
Create an error object with a specified name and message.
|
||||
|
||||
In native Lua, errors can only be string values. At Roblox, we can take advantage of throwing
|
||||
error objects to provide structured information about problems that occur.
|
||||
|
||||
The tags table stores serializable information about an error which can be provided when it is
|
||||
thrown, and later passed to a logging endpoint.
|
||||
|
||||
Throwing an error instance captures its stack trace, avoiding the need to explicitly use xpcall.
|
||||
|
||||
@usage In general, errors should not be used during normal control flow.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local class = require(Dash.class)
|
||||
local format = require(Dash.format)
|
||||
local join = require(Dash.join)
|
||||
|
||||
--[[
|
||||
Create a new Error instance.
|
||||
@param name The name of the error
|
||||
@param string A message for the error which will be formatted using Dash.format
|
||||
@param tags Any fixed tags
|
||||
]]
|
||||
local Error = class("Error", function(name: string, message: string, tags: Types.Table?)
|
||||
return {
|
||||
name = name,
|
||||
message = message or "An error occurred",
|
||||
tags = tags or {}
|
||||
}
|
||||
end)
|
||||
|
||||
function Error:toString(): string
|
||||
return format("{}: {}\n{}", self.name, format(self.message, self.tags), self.stack)
|
||||
end
|
||||
|
||||
--[[
|
||||
Return a new error instance containing the tags provided joined to any existing tags of the
|
||||
current error instance.
|
||||
]]
|
||||
function Error:joinTags(tags: Types.Table?): Error
|
||||
return Error.new(self.name, self.message, join(self.tags, tags))
|
||||
end
|
||||
|
||||
--[[
|
||||
Throw an error.
|
||||
|
||||
The stack of the error is captured and stored.
|
||||
|
||||
If additional tags are provided, a new error is created with the joined tags of
|
||||
this instance.
|
||||
]]
|
||||
function Error:throw(tags: Types.Table?)
|
||||
local instance = self:joinTags(tags)
|
||||
instance.stack = debug.traceback()
|
||||
error(instance)
|
||||
end
|
||||
|
||||
-- TODO Luau: Define class types automatically
|
||||
export type Error = typeof(Error.new("", ""))
|
||||
|
||||
return Error
|
||||
@@ -0,0 +1,13 @@
|
||||
--[[
|
||||
A symbol representing nothing, that can be used in place of nil as a key or value of a table,
|
||||
where nil is illegal.
|
||||
|
||||
Utility functions can check for the None symbol and treat it like a nil value.
|
||||
|
||||
@usage Use cases include:
|
||||
1. Creating an ordered list with undefined values in it
|
||||
2. Creating a map with a key pointing to a nil value
|
||||
]]
|
||||
local Symbol = require(script.Parent.Symbol)
|
||||
local None = Symbol.new("None")
|
||||
return None
|
||||
@@ -0,0 +1,32 @@
|
||||
--[[
|
||||
Create a symbol with a specified name. Upper snake-case is recommended as the symbol is a
|
||||
constant, unless you are linking the symbol conceptually to a different string.
|
||||
|
||||
Symbols are useful when you want a value that isn't equal to any other type, for example if you
|
||||
want to store a unique property on an object that won't be accidentally accessed with a simple
|
||||
string lookup.
|
||||
|
||||
@example
|
||||
local CHEESE = Symbol.new("CHEESE")
|
||||
local FAKE_CHEESE = Symbol.new("CHEESE")
|
||||
print(CHEESE == CHEESE) --> true
|
||||
print(CHEESE == FAKE_CHEESE) --> false
|
||||
print(tostring(CHEESE)) --> "Symbol.new(CHEESE)"
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local class = require(Dash.class)
|
||||
|
||||
local Symbol = class("Symbol", function(name: string)
|
||||
return {
|
||||
name = name
|
||||
}
|
||||
end)
|
||||
|
||||
function Symbol:toString(): string
|
||||
return ("Symbol(%s)"):format(self.name)
|
||||
end
|
||||
|
||||
-- TODO Luau: Define class types automatically
|
||||
export type Symbol = typeof(Symbol.new(""))
|
||||
|
||||
return Symbol
|
||||
@@ -0,0 +1,19 @@
|
||||
-- TODO Luau: Support these globally
|
||||
-- A table with values of type _Value_ and numeric keys 1..n with no gaps
|
||||
export type Array<Value> = {[number]: Value}
|
||||
-- A table with values of type _Value_ and numeric keys, possibly with gaps
|
||||
export type Args<Value> = {[number]: Value}
|
||||
-- A table with keys of type _Key_ and values of type _Value_
|
||||
export type Map<Key, Value> = {[Key]: Value}
|
||||
-- A table with keys of a fixed type _Key_ and a boolean value representing membership of the set (default is false)
|
||||
export type Set<Key> = {[Key]: boolean}
|
||||
-- A table of any type
|
||||
export type Table = {[any]: any}
|
||||
-- A class has a constructor returning an instance of _Object_ type
|
||||
export type Class<Object> = {
|
||||
new: () -> Object
|
||||
}
|
||||
-- Represents a function which takes any arguments and returns any value
|
||||
export type AnyFunction = () -> any
|
||||
|
||||
return {}
|
||||
@@ -0,0 +1,31 @@
|
||||
--[[
|
||||
Adds new elements to the _target_ Array from subsequent Array arguments in left-to-right order.
|
||||
|
||||
Arguments which are `nil` or None are skipped.
|
||||
|
||||
@mutable target
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local None = require(Dash.None)
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local forEachArgs = require(Dash.forEachArgs)
|
||||
local forEach = require(Dash.forEach)
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Add varags typings
|
||||
local function append(target: Types.Array<any>, ...): Types.Array<any>
|
||||
assertEqual(typeof(target), "table", [[Attempted to call Dash.append with argument #1 of type {left:?} not {right:?}]])
|
||||
forEachArgs(function(list: Types.Table?)
|
||||
if list == None or list == nil then
|
||||
return
|
||||
end
|
||||
forEach(list, function(value: any)
|
||||
insert(target, value)
|
||||
end)
|
||||
end, ...)
|
||||
return target
|
||||
end
|
||||
|
||||
return append
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
--[[
|
||||
Performs a simple equality check and throws an error if _left_ is not equal to _right_.
|
||||
|
||||
The formatted error message can be customized, which by default provides a serialization of
|
||||
both inputs using Dash.pretty.
|
||||
|
||||
The `left` and `right` values are available to be referenced in the formatted message.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
|
||||
local function assertEqual(left: any, right: any, formattedErrorMessage: string?)
|
||||
if left ~= right then
|
||||
local Error = require(Dash.Error)
|
||||
local TypeError = Error.new("AssertError", formattedErrorMessage or [[Left {left:?} does not equal right {right:?}]])
|
||||
TypeError:throw({
|
||||
left = left,
|
||||
right = right
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return assertEqual
|
||||
@@ -0,0 +1,73 @@
|
||||
--[[
|
||||
Adds new values to _target_ from subsequent Table arguments in left-to-right order.
|
||||
|
||||
The None symbol can be used to remove existing elements in the target.
|
||||
|
||||
@param ... any number of other tables
|
||||
@example
|
||||
local characters = {
|
||||
Frodo = {
|
||||
name = "Frodo Baggins",
|
||||
team = "blue"
|
||||
},
|
||||
Boromir = {
|
||||
score = 5
|
||||
}
|
||||
}
|
||||
local otherCharacters = {
|
||||
Frodo = {
|
||||
team = "red",
|
||||
score = 10
|
||||
},
|
||||
Bilbo = {
|
||||
team = "yellow",
|
||||
},
|
||||
Boromir = {
|
||||
score = {1, 2, 3}
|
||||
}
|
||||
}
|
||||
local result = assign(characters, otherCharacters)
|
||||
print(result) --> {
|
||||
Frodo = {
|
||||
team = "red",
|
||||
score = 10
|
||||
},
|
||||
Bilbo = {
|
||||
team = "yellow"
|
||||
},
|
||||
Boromir = {
|
||||
score = {1, 2, 3}
|
||||
}
|
||||
}
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local None = require(Dash.None)
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local forEach = require(Dash.forEach)
|
||||
local forEachArgs = require(Dash.forEachArgs)
|
||||
|
||||
|
||||
-- TODO Luau: Support typing varargs
|
||||
-- TODO Luau: Support function generics
|
||||
local function assign(target: Types.Table, ...): Types.Table
|
||||
assertEqual(typeof(target), "table", [[Attempted to call Dash.assign with argument #1 of type {left:?} not {right:?}]])
|
||||
-- Iterate through the varags in order
|
||||
forEachArgs(function(input: Types.Table?)
|
||||
-- Ignore items which are not defined
|
||||
if input == nil or input == None then
|
||||
return
|
||||
end
|
||||
-- Iterate through each key of the input and assign to target at the same key
|
||||
forEach(input, function(value, key)
|
||||
if value == None then
|
||||
target[key] = nil
|
||||
else
|
||||
target[key] = value
|
||||
end
|
||||
end)
|
||||
end, ...)
|
||||
return target
|
||||
end
|
||||
|
||||
return assign
|
||||
@@ -0,0 +1,288 @@
|
||||
--[[
|
||||
Create a class called _name_ with the specified _constructor_. The constructor should return a
|
||||
plain table which will be turned into an instance of _Class_ from a call to `Class.new(...)`.
|
||||
|
||||
@example
|
||||
-- Create a simple Vehicle class
|
||||
local Vehicle = class("Vehicle", function(wheelCount: number) return
|
||||
{
|
||||
speed = 0,
|
||||
wheelCount = wheelCount
|
||||
}
|
||||
end)
|
||||
function Vehicle:drive(speed)
|
||||
self.speed = speed
|
||||
end
|
||||
-- Create a car instance
|
||||
local car = Vehicle.new(4)
|
||||
car.wheelCount --> 4
|
||||
car.speed --> 0
|
||||
-- Drive the car
|
||||
car:drive(10)
|
||||
car.speed --> 10
|
||||
|
||||
@usage When using Dash classes, private fields should be prefixed with `_` to avoid accidental access.
|
||||
@usage A private field should only be accessed by a method of the class itself, though Rodash
|
||||
does not restrict this in code.
|
||||
@usage Public fields are recommended when there is no complex access logic e.g. `position.x`
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
|
||||
local function throwNotImplemented(tags: Types.Table)
|
||||
local Error = require(Dash.Error)
|
||||
local NotImplemented = Error.new("NotImplemented", [[The method "{methodName}" is not implemented on the class "{className}"]])
|
||||
NotImplemented:throw(tags)
|
||||
end
|
||||
|
||||
export type Constructor = () -> Types.Table
|
||||
|
||||
local function class(name: string, constructor: Constructor?)
|
||||
constructor = constructor or function()
|
||||
return {}
|
||||
end
|
||||
local Class = {
|
||||
name = name
|
||||
}
|
||||
--[[
|
||||
Return a new instance of the class, passing any arguments to the specified constructor.
|
||||
@example
|
||||
local Car = class("Car", function(speed)
|
||||
return {
|
||||
speed = speed
|
||||
}
|
||||
end)
|
||||
local car = Car.new(5)
|
||||
pretty(car) --> 'Car {speed = 5}'
|
||||
]]
|
||||
function Class.new(...)
|
||||
local instance = constructor(...)
|
||||
setmetatable(
|
||||
instance,
|
||||
{
|
||||
__index = Class,
|
||||
__tostring = Class.toString,
|
||||
__eq = Class.equals,
|
||||
__lt = Class.__lt,
|
||||
__le = Class.__le,
|
||||
__add = Class.__add,
|
||||
__sub = Class.__sub,
|
||||
__mul = Class.__mul,
|
||||
__div = Class.__div,
|
||||
__mod = Class.__mod
|
||||
}
|
||||
)
|
||||
instance.Class = Class
|
||||
instance:_init(...)
|
||||
return instance
|
||||
end
|
||||
--[[
|
||||
Run after the instance has been properly initialized, allowing methods on the instance to
|
||||
be used.
|
||||
@example
|
||||
local Vehicle = dash.class("Vehicle", function(wheelCount) return
|
||||
{
|
||||
speed = 0,
|
||||
wheelCount = wheelCount
|
||||
}
|
||||
end)
|
||||
-- Let's define a static private function to generate a unique id for each vehicle.
|
||||
function Vehicle._getNextId()
|
||||
Vehicle._nextId = Vehicle._nextId + 1
|
||||
return Vehicle._nextId
|
||||
end
|
||||
Vehicle._nextId = 0
|
||||
-- A general purpose init function may call other helper methods
|
||||
function Vehicle:_init()
|
||||
self._id = self:_generateId()
|
||||
end
|
||||
-- Assign an id to the new instance
|
||||
function Vehicle:_generateId()
|
||||
return format("#{}: {} wheels", Vehicle._getNextId(), self.wheelCount)
|
||||
end
|
||||
-- Return the id if the instance is represented as a string
|
||||
function Vehicle:toString()
|
||||
return self._id
|
||||
end
|
||||
local car = Vehicle.new(4)
|
||||
tostring(car) --> "#1: 4 wheels"
|
||||
]]
|
||||
function Class:_init()
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns `true` if _value_ is an instance of _Class_ or any sub-class.
|
||||
@example
|
||||
local Vehicle = dash.class("Vehicle", function(wheelCount) return
|
||||
{
|
||||
speed = 0,
|
||||
wheelCount = wheelCount
|
||||
}
|
||||
end)
|
||||
local Car = Vehicle:extend("Vehicle", function()
|
||||
return Vehicle.constructor(4)
|
||||
end)
|
||||
local car = Car.new()
|
||||
car.isInstance(Car) --> true
|
||||
car.isInstance(Vehicle) --> true
|
||||
car.isInstance(Bike) --> false
|
||||
]]
|
||||
function Class.isInstance(value)
|
||||
local ok, isInstance = pcall(function()
|
||||
local metatable = getmetatable(value)
|
||||
while metatable do
|
||||
if metatable.__index == Class then
|
||||
return true
|
||||
end
|
||||
metatable = getmetatable(metatable.__index)
|
||||
end
|
||||
return false
|
||||
end)
|
||||
return ok and isInstance
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a subclass of _Class_ with a new _name_ that inherits the metatable of _Class_,
|
||||
optionally overriding the _constructor_ and providing additional _decorators_.
|
||||
The super-constructor can be accessed with `Class.constructor`.
|
||||
Super methods can be accessed using `Class.methodName` and should be called with self.
|
||||
@example
|
||||
local Vehicle = dash.class("Vehicle", function(wheelCount) return
|
||||
{
|
||||
speed = 0,
|
||||
wheelCount = wheelCount
|
||||
}
|
||||
end)
|
||||
-- Let's define a static private function to generate a unique id for each vehicle.
|
||||
function Vehicle._getNextId()
|
||||
Vehicle._nextId = Vehicle._nextId + 1
|
||||
return Vehicle._nextId
|
||||
end
|
||||
Vehicle._nextId = 0
|
||||
-- A general purpose init function may call other helper methods
|
||||
function Vehicle:_init()
|
||||
self.id = self:_generateId()
|
||||
end
|
||||
-- Assign an id to the new instance
|
||||
function Vehicle:_generateId()
|
||||
return dash.format("#{}: {} wheels", Vehicle._getNextId(), self.wheelCount)
|
||||
end
|
||||
-- Let's make a Car class which has a special way to generate ids
|
||||
local Car = Vehicle:extend("Vehicle", function()
|
||||
return Vehicle.constructor(4)
|
||||
end)
|
||||
-- Uses the super method to generate a car-specific id
|
||||
function Car:_generateId()
|
||||
self.id = dash.format("Car {}", Vehicle._generateId(self))
|
||||
end
|
||||
local car = Car.new()
|
||||
car.id --> "Car #1: 4 wheels"
|
||||
]]
|
||||
function Class:extend(name: string, constructor)
|
||||
local SubClass = class(name, constructor or Class.new)
|
||||
setmetatable(SubClass, {__index = self})
|
||||
return SubClass
|
||||
end
|
||||
|
||||
--[[
|
||||
Return a string representation of the instance. By default this is the _name_ field (or the
|
||||
Class name if this is not defined), but the method can be overridden.
|
||||
@example
|
||||
local Car = class("Car", function(name)
|
||||
return {
|
||||
name = name
|
||||
}
|
||||
end)
|
||||
|
||||
local car = Car.new()
|
||||
car:toString() --> 'Car'
|
||||
tostring(car) --> 'Car'
|
||||
print("Hello " .. car) -->> Hello Car
|
||||
local bob = Car.new("Bob")
|
||||
bob:toString() --> 'Bob'
|
||||
tostring(bob) --> 'Bob'
|
||||
print("Hello " .. bob) -->> Hello Bob
|
||||
@example
|
||||
local NamedCar = class("NamedCar", function(name)
|
||||
return {
|
||||
name = name
|
||||
}
|
||||
end)
|
||||
function NamedCar:toString()
|
||||
return "Car called " .. self.name
|
||||
end
|
||||
local bob = NamedCar.new("Bob")
|
||||
bob:toString() --> 'Car called Bob'
|
||||
tostring(bob) --> 'Car called Bob'
|
||||
print("Hello " .. bob) -->> Hello Car called Bob
|
||||
]]
|
||||
function Class:toString()
|
||||
return self.name
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns `true` if `self` is considered equal to _other_. This replaces the `==` operator
|
||||
on instances of this class, and can be overridden to provide a custom implementation.
|
||||
]]
|
||||
function Class:equals(other)
|
||||
return rawequal(self, other)
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns `true` if `self` is considered less than _other_. This replaces the `<` operator
|
||||
on instances of this class, and can be overridden to provide a custom implementation.
|
||||
]]
|
||||
function Class:__lt(other)
|
||||
throwNotImplemented({
|
||||
methodName = "__lt",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns `true` if `self` is considered less than or equal to _other_. This replaces the
|
||||
`<=` operator on instances of this class, and can be overridden to provide a custom
|
||||
implementation.
|
||||
]]
|
||||
function Class:__le(other)
|
||||
throwNotImplemented({
|
||||
methodName = "__le",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
|
||||
function Class:__add()
|
||||
throwNotImplemented({
|
||||
methodName = "__add",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
function Class:__sub()
|
||||
throwNotImplemented({
|
||||
methodName = "__sub",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
function Class:__mul()
|
||||
throwNotImplemented({
|
||||
methodName = "__mul",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
function Class:__div()
|
||||
throwNotImplemented({
|
||||
methodName = "__div",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
function Class:__mod()
|
||||
throwNotImplemented({
|
||||
methodName = "__mod",
|
||||
className = name
|
||||
})
|
||||
end
|
||||
|
||||
return Class
|
||||
end
|
||||
|
||||
return class
|
||||
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
Collect returns a new Table derived from _input_ by iterating through its pairs and calling
|
||||
the handler on each `(key, child)` tuple.
|
||||
|
||||
The handler should return a new `(newKey, value)` tuple to be inserted into the returned Table,
|
||||
or `nil` if no value should be added.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type CollectHandler = () -> (any, any)
|
||||
|
||||
local function collect(input: Types.Table, handler: CollectHandler): Types.Map<any, any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.collect with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.collect with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, child in iterator(input) do
|
||||
local outputKey, outputValue = handler(key, child)
|
||||
if outputKey ~= nil then
|
||||
result[outputKey] = outputValue
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return collect
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
Collect returns a new Array derived from _input_ by iterating through its pairs and calling
|
||||
the handler on each `(key, child)` tuple.
|
||||
|
||||
The handler should return a new value to be pushed onto the end of the result array, or `nil`
|
||||
if no value should be added.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function collectArray(input: Types.Table, handler: Types.AnyFunction)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.collectArray with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.collectArray with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, child in iterator(input) do
|
||||
local outputValue = handler(key, child)
|
||||
if outputValue ~= nil then
|
||||
insert(result, outputValue)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return collectArray
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
--[[
|
||||
Build a set from the entries of the _input_ Table, calling _handler_ on each entry and using
|
||||
the returned value as an element to add to the set.
|
||||
|
||||
If _handler_ is not provided, values of `input` are used as elements.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function collectSet(input: Types.Table, handler: Types.AnyFunction?)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.collectSet with argument #1 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, child in iterator(input) do
|
||||
local outputValue
|
||||
if handler == nil then
|
||||
outputValue = child
|
||||
else
|
||||
outputValue = handler(key, child)
|
||||
end
|
||||
if outputValue ~= nil then
|
||||
result[outputValue] = true
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return collectSet
|
||||
@@ -0,0 +1,36 @@
|
||||
--[[
|
||||
Returns a function that calls the argument functions in left-right order on an input, passing
|
||||
the return of the previous function as argument(s) to the next.
|
||||
|
||||
@example
|
||||
local function fry(item)
|
||||
return "fried " .. item
|
||||
end
|
||||
local function cheesify(item)
|
||||
return "cheesy " .. item
|
||||
end
|
||||
local prepare = compose(fry, cheesify)
|
||||
prepare("nachos") --> "cheesy fried nachos"
|
||||
]]
|
||||
-- TODO Luau: Support generic functions
|
||||
-- TODO Luau: Support varargs
|
||||
--: <A>((...A -> ...A)[]) -> ...A -> A
|
||||
local Dash = script.Parent
|
||||
local identity = require(Dash.identity)
|
||||
|
||||
local function compose(...)
|
||||
local fnCount = select("#", ...)
|
||||
if fnCount == 0 then
|
||||
return identity
|
||||
end
|
||||
local fns = {...}
|
||||
return function(...)
|
||||
local result = {fns[1](...)}
|
||||
for i = 2, fnCount do
|
||||
result = {fns[i](unpack(result))}
|
||||
end
|
||||
return unpack(result)
|
||||
end
|
||||
end
|
||||
|
||||
return compose
|
||||
@@ -0,0 +1,13 @@
|
||||
--[[
|
||||
Returns a shallow copy of the _input_ Table.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assign = require(Dash.assign)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function copy(input: Types.Table): Types.Table
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.copy with argument #1 of type {left:?} not {right:?}]])
|
||||
return assign({}, input)
|
||||
end
|
||||
return copy
|
||||
@@ -0,0 +1,90 @@
|
||||
--[[
|
||||
Get information about the number of times references to the same table values appear in a data structure.
|
||||
|
||||
Operates on cyclic structures, and returns a Cycles object for a given _value_ by walking it recursively.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local includes = require(Dash.includes)
|
||||
local join = require(Dash.join)
|
||||
local keys = require(Dash.keys)
|
||||
|
||||
local sort = table.sort
|
||||
|
||||
export type Cycles = {
|
||||
-- A set of tables which were visited recursively
|
||||
visited: Types.Set<Types.Table>,
|
||||
-- A map from table to unique index in visit order
|
||||
refs: Types.Map<Types.Table, number>,
|
||||
-- The number to use for the next unique table visited
|
||||
nextRef: number,
|
||||
-- An array of keys which should not be visited
|
||||
omit: Types.Array<any>,
|
||||
}
|
||||
|
||||
local function getDefaultCycles(): Cycles
|
||||
return {
|
||||
visited = {},
|
||||
refs = {},
|
||||
nextRef = 1,
|
||||
omit = {},
|
||||
}
|
||||
end
|
||||
|
||||
-- TODO Luau: Improve type inference to a point that this definition does not produce so many type errors
|
||||
-- TYPED: local function cycles(value: any, depth: number?, initialCycles: Cycles?): Cycles
|
||||
local function cycles(input: any, depth: number?, initialCycles: any): Cycles?
|
||||
if depth == -1 then
|
||||
return initialCycles
|
||||
end
|
||||
|
||||
if typeof(input) == "table" then
|
||||
local childCycles = initialCycles or getDefaultCycles()
|
||||
|
||||
if childCycles.visited[input] then
|
||||
-- We have already visited the table, so check if it has a reference
|
||||
if not childCycles.refs[input] then
|
||||
-- If not, create one as it is present at least twice
|
||||
childCycles.refs[input] = childCycles.nextRef
|
||||
childCycles.nextRef += 1
|
||||
end
|
||||
return nil
|
||||
else
|
||||
-- We haven't yet visited the table, so recurse
|
||||
childCycles.visited[input] = true
|
||||
-- Visit in order to preserve reference consistency
|
||||
local inputKeys = keys(input)
|
||||
sort(inputKeys, function(left, right)
|
||||
if typeof(left) == "number" and typeof(right) == "number" then
|
||||
return left < right
|
||||
else
|
||||
return tostring(left) < tostring(right)
|
||||
end
|
||||
end)
|
||||
for _, key in ipairs(inputKeys) do
|
||||
local value = input[key]
|
||||
if includes(childCycles.omit, key) then
|
||||
-- Don't visit omitted keys
|
||||
continue
|
||||
end
|
||||
-- TODO Luau: support type narrowring with "and"
|
||||
-- TYPED: cycles(key, depth and depth - 1 or nil, childCycles)
|
||||
-- TYPED: cycles(value, depth and depth - 1 or nil, childCycles)
|
||||
-- Recurse through both the keys and values of the table
|
||||
if depth then
|
||||
cycles(key, depth - 1, childCycles)
|
||||
cycles(value, depth - 1, childCycles)
|
||||
else
|
||||
cycles(key, nil, childCycles)
|
||||
cycles(value, nil, childCycles)
|
||||
end
|
||||
end
|
||||
end
|
||||
return childCycles
|
||||
else
|
||||
-- Non-tables do not have cycles
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return cycles
|
||||
@@ -0,0 +1,15 @@
|
||||
--[[
|
||||
Checks if _input_ ends with the string _suffix_.
|
||||
@example endsWith("Fun Roblox Games", "Games") --> true
|
||||
@example endsWith("Bad Roblox Memes", "Games") --> false
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function endsWith(input: string, suffix: string)
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.endsWith with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(suffix), "string", [[Attempted to call Dash.endsWith with argument #2 of type {left:?} not {right:?}]])
|
||||
return input:sub(-suffix:len()) == suffix
|
||||
end
|
||||
|
||||
return endsWith
|
||||
@@ -0,0 +1,27 @@
|
||||
--[[
|
||||
Filter the _input_ Table by calling the handler on each `(child, index)` tuple.
|
||||
|
||||
For an Array input, the order of elements is prevered in the output.
|
||||
|
||||
The handler should return truthy to preserve the value in the resulting Table.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type FilterHandler = (any, any) -> boolean
|
||||
|
||||
local function filter(input: Types.Table, handler: FilterHandler): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.filter with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.filter with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for index, child in iterator(input) do
|
||||
if handler(child, index) then
|
||||
table.insert(result, child)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return filter
|
||||
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
Returns an element in the _input_ Table that the handler returns `true` for, when passed the
|
||||
`(child, key)` entry.
|
||||
|
||||
Returns nil if no entires satisfy the condition.
|
||||
|
||||
For an Array, this first matching element is returned.
|
||||
|
||||
For a Map, an arbitrary matching element is returned if it exists.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type FindHandler = (any, any) -> boolean
|
||||
|
||||
local function find(input: Types.Table, handler: FindHandler)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.find with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.find with argument #2 of type {left:?} not {right:?}]])
|
||||
for key, child in iterator(input) do
|
||||
if handler(child, key) then
|
||||
return child
|
||||
end
|
||||
end
|
||||
end
|
||||
return find
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
--[[
|
||||
Returns the index of the first element in the _input_ Array that the handler returns `true` for,
|
||||
when passed the `(child, key)` entry.
|
||||
|
||||
Returns nil if no entires satisfy the condition.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type FindHandler = (any, any) -> boolean
|
||||
|
||||
local function findIndex(input: Types.Array<any>, handler: FindHandler)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.findIndex with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.findIndex with argument #2 of type {left:?} not {right:?}]])
|
||||
for key, child in ipairs(input) do
|
||||
if handler(child, key) then
|
||||
return key
|
||||
end
|
||||
end
|
||||
end
|
||||
return findIndex
|
||||
@@ -0,0 +1,22 @@
|
||||
--[[
|
||||
Flattens the input array by a single level.
|
||||
|
||||
Outputs a new Array of elements merged from the _input_ Array arguments in left-to-right order.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local append = require(Dash.append)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local forEach = require(Dash.forEach)
|
||||
|
||||
-- TODO Luau: Support function generics
|
||||
local function flat(input: Types.Array<Types.Array<any>>): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.flat with argument #1 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
forEach(input, function(childArray: Types.Array<any>)
|
||||
append(result, childArray)
|
||||
end)
|
||||
return result
|
||||
end
|
||||
|
||||
return flat
|
||||
@@ -0,0 +1,25 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Table.
|
||||
|
||||
If the table is an Array, it iterates in order 1..n.
|
||||
|
||||
If the table is a Map, the keys are visited in an arbitrary order.
|
||||
|
||||
Calls the _handler_ for each entry.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
export type ForEachHandler<Value> = (Value, number) -> ()
|
||||
-- TODO Luau: Support function generics
|
||||
--local function forEach<Value>(input: Types.Array<Value>, handler: ForEachHandler<Value>)
|
||||
local function forEach(input: Types.Table, handler: Types.AnyFunction)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.forEach with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.forEach with argument #2 of type {left:?} not {right:?}]])
|
||||
for key, value in iterator(input) do
|
||||
handler(value, key)
|
||||
end
|
||||
end
|
||||
return forEach
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
--[[
|
||||
Iterates through the tail arguments in order, including nil values up to the argument list length.
|
||||
Calls the _handler_ for each entry.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
export type ForEachArgsHandler<Value> = (Value, number) -> ()
|
||||
-- TODO Luau: Support function generics
|
||||
-- TODO Luau: Support vararg types
|
||||
--local function forEachArgs<Value>(handler: ForEachArgsHandler<Value>, ...: Types.Args<Value>)
|
||||
|
||||
local function forEachArgs(handler: Types.AnyFunction, ...)
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.forEachArgs with argument #1 of type {left:?} not {right:?}]])
|
||||
for index = 1, select('#', ...) do
|
||||
handler(select(index, ...), index)
|
||||
end
|
||||
end
|
||||
return forEachArgs
|
||||
@@ -0,0 +1,88 @@
|
||||
--[[
|
||||
Returns the _format_ string with placeholders `{...}` substituted with readable representations
|
||||
of the subsequent arguments.
|
||||
This function is a simpler & more powerful version of `string.format`, inspired by `format!`
|
||||
in Rust.
|
||||
|
||||
* `{}` formats and prints the next argument using `:format()` if available, or a suitable
|
||||
default representation depending on its type.
|
||||
* `{blah}` formats and prints the key "blah" of the 1st argument
|
||||
* `{2}` formats and prints the 2nd argument.
|
||||
* `{#2}` prints the length of the 2nd argument.
|
||||
Display parameters can be combined after a `:` in the curly braces. Any format parameters used
|
||||
in `string.format` can be used here, along with these extras:
|
||||
* `{:?}` formats any value using `pretty`.
|
||||
* `{:#?}` formats any value using multiline `pretty`.
|
||||
@example
|
||||
local props = {"teeth", "claws", "whiskers", "tail"}
|
||||
format("{:?} is in {:#?}", "whiskers", props)
|
||||
-> '"whiskers" is in {"teeth", "claws", "whiskers", "tail"}'
|
||||
@example
|
||||
format("The time is {:02}:{:02}", 2, 4) -> "The time is 02:04"
|
||||
@example
|
||||
format("The color blue is #{:06X}", 255) -> "The color blue is #0000FF"
|
||||
@usage Escape `{` with `{{` and `}` similarly with `}}`.
|
||||
@usage See [https://developer.roblox.com/articles/Format-String](https://developer.roblox.com/articles/Format-String)
|
||||
for complete list of formating options and further use cases.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local formatValue = require(Dash.formatValue)
|
||||
local splitOn = require(Dash.splitOn)
|
||||
local startsWith = require(Dash.startsWith)
|
||||
|
||||
local concat = table.concat
|
||||
local insert = table.insert
|
||||
|
||||
local function format(formatString: string, ...)
|
||||
assertEqual(typeof(formatString), "string", [[Attempted to call Dash.format with argument #1 of type {left:?} not {right:?}]])
|
||||
local args = {...}
|
||||
local argIndex = 1
|
||||
local texts, subs = splitOn(formatString, "{[^{}]*}")
|
||||
local result = {}
|
||||
-- Iterate through possible curly-brace matches, ignoring escaped and substituting valid ones
|
||||
for i, text in pairs(texts) do
|
||||
local unescaped = text:gsub("{{", "{"):gsub("}}", "}")
|
||||
insert(result, unescaped)
|
||||
local placeholder = subs[i] and subs[i]:sub(2, -2)
|
||||
if placeholder then
|
||||
-- Ensure that the curly braces have not been escaped
|
||||
local escapeMatch = text:gmatch("{+$")()
|
||||
local isEscaped = escapeMatch and #escapeMatch % 2 == 1
|
||||
if not isEscaped then
|
||||
-- Split the placeholder into left & right parts pivoting on the central ":"
|
||||
local placeholderSplit = splitOn(placeholder, ":")
|
||||
local isLength = startsWith(placeholderSplit[1], "#")
|
||||
local argString = isLength and placeholderSplit[1]:sub(2) or placeholderSplit[1]
|
||||
local nextIndex = tonumber(argString)
|
||||
local displayString = placeholderSplit[2]
|
||||
local arg = "nil"
|
||||
if nextIndex then
|
||||
-- Return the next argument
|
||||
arg = args[nextIndex]
|
||||
elseif argString:len() > 0 then
|
||||
-- Print a child key of the 1st argument
|
||||
local argChild = args[1] and args[1][argString]
|
||||
if argChild ~= nil then
|
||||
arg = argChild
|
||||
end
|
||||
else
|
||||
arg = args[argIndex]
|
||||
argIndex = argIndex + 1
|
||||
end
|
||||
if isLength then
|
||||
arg = #arg
|
||||
end
|
||||
-- Format the selected value
|
||||
insert(result, formatValue(arg, displayString or ""))
|
||||
else
|
||||
local unescapedSub = placeholder
|
||||
insert(result, unescapedSub)
|
||||
end
|
||||
end
|
||||
end
|
||||
return concat(result, "")
|
||||
end
|
||||
|
||||
return format
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
Format a specific _value_ using the specified _displayString_.
|
||||
@example
|
||||
formatValue(255, "06X") --> "0000FF"
|
||||
@example
|
||||
formatValue(255.5) --> "255.5"
|
||||
@see `format` - for a full description of valid display strings.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function formatValue(value: any, displayString: string): string
|
||||
displayString = displayString or ""
|
||||
assertEqual(typeof(displayString), "string", [[Attempted to call Dash.formatValue with argument #2 of type {left:?} not {right:?}]])
|
||||
-- Inline require to prevent infinite require cycle
|
||||
local displayTypeStart, displayTypeEnd = displayString:find("[A-Za-z#?]+")
|
||||
if displayTypeStart then
|
||||
local displayType = displayString:sub(displayTypeStart, displayTypeEnd)
|
||||
local formatAsString =
|
||||
"%" .. displayString:sub(1, displayTypeStart - 1) .. displayString:sub(displayTypeEnd + 1) .. "s"
|
||||
-- Pretty print values
|
||||
local pretty = require(Dash.pretty)
|
||||
if displayType == "#?" then
|
||||
-- Multiline print a value
|
||||
return formatAsString:format(pretty(value, {multiline = true}))
|
||||
elseif displayType == "?" then
|
||||
-- Inspect a value
|
||||
return formatAsString:format(pretty(value))
|
||||
end
|
||||
return ("%" .. displayString):format(value)
|
||||
else
|
||||
local displayType = "s"
|
||||
if type(value) == "number" then
|
||||
-- Correctly display floats or integers
|
||||
local _, fraction = math.modf(value)
|
||||
displayType = fraction == 0 and "d" or "f"
|
||||
end
|
||||
return ("%" .. displayString .. displayType):format(tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
return formatValue
|
||||
@@ -0,0 +1,77 @@
|
||||
--[[
|
||||
Returns a new read-only view of _object_ which prevents any values from being changed.
|
||||
|
||||
@param name The name of the object for improved error message readability.
|
||||
@param throwIfMissing If `true` then access to a missing key will also throw.
|
||||
|
||||
@note
|
||||
Unfortunately you cannot iterate using `pairs` or `ipairs` on frozen objects because Luau
|
||||
doesn't support defining these custom iterators in metatables.
|
||||
|
||||
@example
|
||||
local drink = freeze("Ice Cream", {
|
||||
flavor = "mint",
|
||||
topping = "sprinkles"
|
||||
}, true)
|
||||
print(drink.flavor) --> "mint"
|
||||
drink.flavor = "vanilla"
|
||||
--!> ReadonlyKey: Attempt to write to readonly key "flavor" (a string) of frozen object "Ice Cream"`
|
||||
print(drink.syrup) --> nil
|
||||
--!> `MissingKey: Attempt to read missing key "syrup" (a string) of frozen object "Ice Cream"`
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local Error = require(Dash.Error)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local format = require(Dash.format)
|
||||
|
||||
-- TODO Luau: Improve type inference to make these not need to be any
|
||||
local ReadonlyKey: any = Error.new("ReadonlyKey", "Attempted to write to readonly key {key:?} of frozen object {objectName:?}")
|
||||
local MissingKey: any = Error.new("MissingKey", "Attempted to read missing key {key:?} of frozen object {objectName:?}")
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
-- TODO Luau: Support generic extends syntax
|
||||
-- TYPED: local function freeze<T extends Types.Table>(objectName: string, object: T, throwIfMissing: boolean?): T
|
||||
local function freeze(objectName: string, object: Types.Table, throwIfMissing: boolean?)
|
||||
assertEqual(typeof(objectName), "string", [[Attempted to call Dash.freeze with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(object), "table", [[Attempted to call Dash.freeze with argument #2 of type {left:?} not {right:?}]])
|
||||
-- We create a proxy so that the underlying object is not affected
|
||||
local proxy = {}
|
||||
setmetatable(
|
||||
proxy,
|
||||
{
|
||||
__index = function(_, key: any)
|
||||
local value = object[key]
|
||||
if value == nil and throwIfMissing then
|
||||
-- Tried to read a key which isn't present in the underlying object
|
||||
MissingKey:throw({
|
||||
key = key,
|
||||
objectName = objectName
|
||||
})
|
||||
end
|
||||
return value
|
||||
end,
|
||||
__newindex = function(_, key: any)
|
||||
-- Tried to write to any key
|
||||
ReadonlyKey:throw({
|
||||
key = key,
|
||||
objectName = objectName
|
||||
})
|
||||
end,
|
||||
__len = function()
|
||||
return #object
|
||||
end,
|
||||
__tostring = function()
|
||||
return format("Frozen({})", objectName)
|
||||
end,
|
||||
__call = function(_, ...)
|
||||
-- TODO Luau: Gated check for if a function has a __call value
|
||||
local callable: any = object
|
||||
return callable(...)
|
||||
end
|
||||
}
|
||||
)
|
||||
return proxy
|
||||
end
|
||||
|
||||
return freeze
|
||||
@@ -0,0 +1,25 @@
|
||||
--[[
|
||||
Returns a value of the _input_ Table at the _key_ provided.
|
||||
|
||||
If the key is missing, the value is acquired from the _getValue_ handler,
|
||||
added to the _input_ Table and returned.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local Error = require(Dash.Error)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local format = require(Dash.format)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type GetValueHandler = (any) -> any
|
||||
|
||||
local function getOrSet(input: Types.Table, key: any, getValue: GetValueHandler)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.getOrSet with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(key == nil, false, [[Attempted to call Dash.getOrSet with a nil key argument]])
|
||||
assertEqual(typeof(getValue), "function", [[Attempted to call Dash.getOrSet with argument #3 of type {left:?} not {right:?}]])
|
||||
if input[key] == nil then
|
||||
input[key] = getValue(input, key)
|
||||
end
|
||||
return input[key]
|
||||
end
|
||||
return getOrSet
|
||||
@@ -0,0 +1,39 @@
|
||||
--[[
|
||||
Groups values in the _input_ Table by their _getKey_ value.
|
||||
|
||||
Each value of the result Table is an Array of values from the _input_ Table which were assigned
|
||||
the corresponding key.
|
||||
|
||||
If _getKey_ is a function, it is called with each `(child, key)` entry and uses the return
|
||||
value as the corresponding key to insert at in the result Table. Otherwise, the _getKey_ value
|
||||
is used directly as the key itself.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function groupBy(input: Dash.Table, getKey: any)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.groupBy with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(getKey == nil, false, [[Attempted to call Dash.groupBy with a nil getKey argument]])
|
||||
local result = {}
|
||||
for key, child in pairs(input) do
|
||||
local groupKey
|
||||
if typeof(getKey) == "function" then
|
||||
groupKey = getKey(child, key)
|
||||
else
|
||||
groupKey = child[getKey]
|
||||
end
|
||||
if groupKey ~= nil then
|
||||
if result[groupKey] ~= nil then
|
||||
insert(result[groupKey], child)
|
||||
else
|
||||
result[groupKey] = {child}
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return groupBy
|
||||
@@ -0,0 +1,11 @@
|
||||
--[[
|
||||
The identity function, which simply returns its input parameters.
|
||||
|
||||
Can be used to make it clear that a handler returns its inputs.
|
||||
]]
|
||||
-- TODO Luau: Support typing generic return tuple
|
||||
local function identity(...)
|
||||
return ...
|
||||
end
|
||||
|
||||
return identity
|
||||
@@ -0,0 +1,23 @@
|
||||
--[[
|
||||
Returns `true` if the _item_ exists as a value in the _input_ table.
|
||||
|
||||
A nil _item_ will always return `false`.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function includes(input: Types.Table, item: any): boolean
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.includes with argument #1 of type {left:?} not {right:?}]])
|
||||
if item == nil then
|
||||
return false
|
||||
end
|
||||
for _, child in pairs(input) do
|
||||
if child == item then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return includes
|
||||
@@ -0,0 +1,8 @@
|
||||
local Dash = {}
|
||||
|
||||
-- Require and add the Dash functions to the Dash table
|
||||
for _, fn in pairs(script:GetChildren()) do
|
||||
Dash[fn.Name] = require(fn)
|
||||
end
|
||||
|
||||
return Dash.freeze("Dash", Dash, true)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
--[[
|
||||
Returns `true` if the value can be called i.e. you can write `value(...)`.
|
||||
]]
|
||||
local function isCallable(value: any): boolean
|
||||
return type(value) == "function" or
|
||||
(type(value) == "table" and getmetatable(value) and getmetatable(value).__call ~= nil) or false
|
||||
end
|
||||
|
||||
return isCallable
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
Returns `true` if the first character of _input_ is a lower-case character.
|
||||
|
||||
Throws if the _input_ is not a string or it is the empty string.
|
||||
|
||||
Our current version of Lua unfortunately does not support upper or lower-case detection outside
|
||||
the english alphabet. This function has been implemented to return the expected result once
|
||||
this has been corrected.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function isLowercase(input: string)
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.isLowercase with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(#input > 0, true, [[Attempted to call Dash.isLowercase with an empty string]])
|
||||
local firstLetter = input:sub(1, 1)
|
||||
return firstLetter == firstLetter:lower()
|
||||
end
|
||||
return isLowercase
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
Returns `true` if the first character of _input_ is an upper-case character.
|
||||
|
||||
Throws if the _input_ is not a string or it is the empty string.
|
||||
|
||||
Our current version of Lua unfortunately does not support upper or lower-case detection outside
|
||||
the english alphabet. This function has been implemented to return the expected result once
|
||||
this has been corrected.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function isUppercase(input: string)
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.isUppercase with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(#input > 0, true, [[Attempted to call Dash.isUppercase with an empty string]])
|
||||
local firstLetter = input:sub(1, 1)
|
||||
return firstLetter == firstLetter:upper()
|
||||
end
|
||||
return isUppercase
|
||||
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
Creates a stateful iterator for the _input_ Table, first visting ordered numeric keys 1..n
|
||||
and then the remaining unordered keys in any order.
|
||||
|
||||
@see Dash.iterator - for an iterator that can iterate over an iterable.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
|
||||
local function iterable(input: Types.Table): Types.AnyFunction
|
||||
local currentIndex = 1
|
||||
local inOrderedKeys = true
|
||||
local currentKey
|
||||
local iterateFn
|
||||
iterateFn = function()
|
||||
if inOrderedKeys then
|
||||
local value = input[currentIndex]
|
||||
if value == nil then
|
||||
inOrderedKeys = false
|
||||
else
|
||||
local index = currentIndex
|
||||
currentIndex += 1
|
||||
return index, value
|
||||
end
|
||||
end
|
||||
while true do
|
||||
currentKey = next(input, currentKey)
|
||||
-- Don't re-visit ordered keys 1..n
|
||||
if typeof(currentKey) == "number" and currentKey > 0 and currentKey < currentIndex and currentKey % 1 == 0 then
|
||||
continue
|
||||
end
|
||||
if currentKey == nil then
|
||||
return nil
|
||||
else
|
||||
return currentKey, input[currentKey]
|
||||
end
|
||||
end
|
||||
end
|
||||
return iterateFn
|
||||
end
|
||||
|
||||
return iterable
|
||||
@@ -0,0 +1,29 @@
|
||||
--[[
|
||||
Iterates using a `pairs` iterator for an _input_ Table if zero length, otherwise an `ipairs`
|
||||
iterator for an Array.
|
||||
|
||||
If _input_ is a function it is used as a stateful iterator instead.
|
||||
|
||||
This function can be used to build behaviour that iterates over both Arrays and Maps.
|
||||
|
||||
@see Dash.iterable if you want to iterate over a Table with numeric but un-ordered keys.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
|
||||
local function iterator(input: Types.Table | Types.AnyFunction): Types.AnyFunction
|
||||
if typeof(input) == "function" then
|
||||
return input
|
||||
elseif typeof(input) == "table" then
|
||||
if #input > 0 then
|
||||
return ipairs(input)
|
||||
else
|
||||
return pairs(input)
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return iterator
|
||||
@@ -0,0 +1,18 @@
|
||||
--[[
|
||||
Output a new Map from merging all the keys in the Map arguments in left-to-right order.
|
||||
|
||||
The None symbol can be used to remove existing elements.
|
||||
|
||||
@param ... any number of tables
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assign = require(Dash.assign)
|
||||
|
||||
-- TODO Luau: Support typing varargs
|
||||
-- TODO Luau: Support function generics
|
||||
local function join(...): Types.Map<any, any>
|
||||
return assign({}, ...)
|
||||
end
|
||||
|
||||
return join
|
||||
@@ -0,0 +1,41 @@
|
||||
--[[
|
||||
Creates a shallow clone of the _source_ Map, and copies the values from the _delta_ Map
|
||||
by key, like the join utility.
|
||||
|
||||
However, if any of the values are tables themselves, the joinDeep function is called
|
||||
recursively to produce a new table at the specified key.
|
||||
|
||||
The purpose of this function is to merge nested immutable data using as few table
|
||||
creation operations as possible, making it appropriate for updating state in a reducer.
|
||||
|
||||
The None symbol can be used to remove an existing value.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local None = require(Dash.None)
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local forEach = require(Dash.forEach)
|
||||
local copy = require(Dash.copy)
|
||||
|
||||
-- TODO Luau: Support typing varargs
|
||||
-- TODO Luau: Support function generics
|
||||
local function joinDeep(source: Types.Table, delta: Types.Table): Types.Table
|
||||
assertEqual(typeof(source), "table", [[Attempted to call Dash.joinDeep with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(delta), "table", [[Attempted to call Dash.joinDeep with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = copy(source)
|
||||
-- Iterate through each key of the input and assign to target at the same key
|
||||
forEach(delta, function(value, key)
|
||||
if typeof(source[key]) == "table" and typeof(value) == "table" then
|
||||
-- Only merge tables
|
||||
result[key] = joinDeep(source[key], value)
|
||||
elseif value == None then
|
||||
-- Remove none values
|
||||
result[key] = nil
|
||||
else
|
||||
result[key] = value
|
||||
end
|
||||
end)
|
||||
return result
|
||||
end
|
||||
|
||||
return joinDeep
|
||||
@@ -0,0 +1,31 @@
|
||||
--[[
|
||||
Assigns values in the _input_ Table by their _getKey_ value.
|
||||
|
||||
If _getKey_ is a function, it is called with each `(child, key)` entry and uses the return
|
||||
value as the corresponding key to assign to in the result Table. Otherwise, the _getKey_ value
|
||||
is used directly as the key itself.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local collect = require(Dash.collect)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function keyBy(input: Types.Table, getKey: any): Types.Table
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.keyBy with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(getKey == nil, false, [[Attempted to call Dash.keyBy with a nil getKey argument]])
|
||||
|
||||
return collect(input, function(key, child)
|
||||
local newKey
|
||||
if typeof(getKey) == "function" then
|
||||
newKey = getKey(child, key)
|
||||
else
|
||||
newKey = child[getKey]
|
||||
end
|
||||
return newKey, child
|
||||
end)
|
||||
end
|
||||
return keyBy
|
||||
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Returns an Array of the keys in the _input_ Table.
|
||||
|
||||
If the input is an Array, ordering is preserved.
|
||||
|
||||
If the input is a Map, elements are returned in an arbitrary order.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function keys(input: Types.Table): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.keys with argument #1 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, _ in iterator(input) do
|
||||
insert(result, key)
|
||||
end
|
||||
return result
|
||||
end
|
||||
return keys
|
||||
@@ -0,0 +1,29 @@
|
||||
--[[
|
||||
Returns the last element in the _input_ Array that the handler returns `true` for, when
|
||||
passed the `(child, index)` entry.
|
||||
|
||||
Returns nil if no entires satisfy the condition.
|
||||
|
||||
If handler is not defined, the function simply returns the last element of the Array.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type FindHandler = (any, any) -> boolean
|
||||
|
||||
local function last(input: Types.Array<any>, handler: FindHandler?)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.last with argument #1 of type {left:?} not {right:?}]])
|
||||
for index = #input, 1, -1 do
|
||||
local child = input[index]
|
||||
if not handler then
|
||||
return child
|
||||
end
|
||||
if handler(child, index) then
|
||||
return child
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return last
|
||||
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Makes a string of `length` from `input` by repeating characters from `prefix` at the start of the string.
|
||||
@example leftPad("toast", 6) --> " toast"
|
||||
@example leftPad("2", 2, "0") --> "02"
|
||||
@example leftPad("toast", 10, ":)") --> ":):):toast"
|
||||
@param prefix (default = `" "`)
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function leftPad(input: string, length: number, prefix: string?): string
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.leftPad with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(length), "number", [[Attempted to call Dash.leftPad with argument #2 of type {left:?} not {right:?}]])
|
||||
|
||||
local definedPrefix = prefix or " "
|
||||
assertEqual(typeof(definedPrefix), "string", [[Attempted to call Dash.leftPad with argument #3 of type {left:?} not {right:?}]])
|
||||
|
||||
local padLength = length - input:len()
|
||||
local remainder = padLength % definedPrefix:len()
|
||||
local repetitions = (padLength - remainder) / definedPrefix:len()
|
||||
return string.rep(definedPrefix or " ", repetitions) .. definedPrefix:sub(1, remainder) .. input
|
||||
end
|
||||
|
||||
return leftPad
|
||||
@@ -0,0 +1,38 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Table.
|
||||
|
||||
For an Array input, the elements are visted in order 1..n.
|
||||
|
||||
For a Map input, the elements are visited in an arbitrary order.
|
||||
|
||||
Calls the _handler_ for each entry and constructs a new Table using the same keys but replacing
|
||||
the values with new ones returned from the handler.
|
||||
|
||||
Values returned by _handler_ must be defined.
|
||||
|
||||
@see Dash.collectArray if you want to return nil values.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type MapHandler = (any, number) -> any
|
||||
|
||||
-- TYPED: export type MapHandler<Input, Output> = (Value, number) -> Output
|
||||
-- TYPED: local function map<Input, Output>(input: Types.Array<Input>, fn: MapFn<Input, Output>)<Output>
|
||||
|
||||
local function map(input: Types.Array<any>, handler: MapHandler): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.map with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.map with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, child in iterator(input) do
|
||||
local value = handler(child, key)
|
||||
assertEqual(value == nil, false, [[Returned nil from a Dash.map handler]])
|
||||
result[key] = value
|
||||
end
|
||||
return result
|
||||
end
|
||||
return map
|
||||
@@ -0,0 +1,23 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Array in order 1..n.
|
||||
|
||||
Calls the _handler_ for each entry and returns the first non-nil value returned by the handler.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type MapHandler = (any, number) -> any
|
||||
|
||||
local function mapFirst(input: Types.Array<any>, handler: MapHandler)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.mapFirst with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.mapFirst with argument #2 of type {left:?} not {right:?}]])
|
||||
for index, child in ipairs(input) do
|
||||
local output = handler(child, index)
|
||||
if output ~= nil then
|
||||
return output
|
||||
end
|
||||
end
|
||||
end
|
||||
return mapFirst
|
||||
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Array in reverse in order n..1.
|
||||
|
||||
Calls the _handler_ for each entry and returns the first non-nil value returned by the handler.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type MapHandler = (any, number) -> any
|
||||
|
||||
local function mapLast(input: Types.Array<any>, handler: MapHandler)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.mapLast with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.mapLast with argument #2 of type {left:?} not {right:?}]])
|
||||
for key = #input, 1, -1 do
|
||||
local child = input[key]
|
||||
local output = handler(child, key)
|
||||
if output ~= nil then
|
||||
return output
|
||||
end
|
||||
end
|
||||
end
|
||||
return mapLast
|
||||
@@ -0,0 +1,29 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Table in no particular order.
|
||||
|
||||
Calls the _handler_ for each entry and returns the first non-nil value returned by the handler.
|
||||
|
||||
If _handler_ is nil, the first value visited is returned.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type MapHandler = (any, number) -> any
|
||||
|
||||
local function mapOne(input: Types.Table, handler: MapHandler?)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.mapOne with argument #1 of type {left:?} not {right:?}]])
|
||||
for key, child in pairs(input) do
|
||||
local output
|
||||
if handler then
|
||||
output = handler(child, key)
|
||||
else
|
||||
output = child
|
||||
end
|
||||
if output ~= nil then
|
||||
return output
|
||||
end
|
||||
end
|
||||
end
|
||||
return mapOne
|
||||
@@ -0,0 +1,10 @@
|
||||
--[[
|
||||
A function which does nothing.
|
||||
|
||||
Can be used to make it clear that a handler has no function.
|
||||
]]
|
||||
local function noop()
|
||||
|
||||
end
|
||||
|
||||
return noop
|
||||
@@ -0,0 +1,30 @@
|
||||
--[[
|
||||
Return a new Table made from entries in the _input_ Table whose key is not in the _keys_ Array.
|
||||
|
||||
If the input is an Array, ordering is preserved.
|
||||
|
||||
If the input is a Map, elements are returned in an arbitrary order.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local collectSet = require(Dash.collectSet)
|
||||
local forEach = require(Dash.forEach)
|
||||
|
||||
-- TODO Luau: Support generic functions, then substitute type signature
|
||||
-- TYPED: local function omit<Key, Value>(input: Types.Map<Key, Value>, keys: Types.Array<Key>): Value
|
||||
local function omit(input: Types.Table, keys: Types.Array<any>): Types.Table
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.omit with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(keys), "table", [[Attempted to call Dash.omit with argument #2 of type {left:?} not {right:?}]])
|
||||
local output = {}
|
||||
local keySet = collectSet(keys)
|
||||
-- TYPED: forEach(input, function(child: Value, key: Key)
|
||||
forEach(input, function(child, key)
|
||||
if not keySet[key] then
|
||||
output[key] = input[key]
|
||||
end
|
||||
end)
|
||||
return output
|
||||
end
|
||||
|
||||
return omit
|
||||
@@ -0,0 +1,26 @@
|
||||
--[[
|
||||
Pick entries in the _input_ Table which should remain in the output by calling the handler on
|
||||
each `(child, index)` tuple.
|
||||
|
||||
The handler should return truthy to preserve the value in the resulting Table.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
-- TODO Luau: support generic function definitions
|
||||
export type PickHandler = (any, any) -> boolean
|
||||
|
||||
local function pick(input: Types.Map<any, any>, handler: PickHandler): Types.Map<any, any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.pick with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.pick with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for key, child in iterator(input) do
|
||||
if handler(child, key) then
|
||||
result[key] = child
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
return pick
|
||||
@@ -0,0 +1,189 @@
|
||||
--[[
|
||||
Return a pretty string serialization of _object_.
|
||||
|
||||
This implementation deals with cycles in tables and can neatly display metatables.
|
||||
|
||||
Optionally use an indented multiline string, limit the depth of tables, omit or pick keys.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
|
||||
local append = require(Dash.append)
|
||||
local assign = require(Dash.assign)
|
||||
local cycles = require(Dash.cycles)
|
||||
local includes = require(Dash.includes)
|
||||
local join = require(Dash.join)
|
||||
local map = require(Dash.map)
|
||||
local keys = require(Dash.keys)
|
||||
local slice = require(Dash.slice)
|
||||
|
||||
local concat = table.concat
|
||||
local insert = table.insert
|
||||
local sort = table.sort
|
||||
|
||||
export type PrettyOptions = {
|
||||
-- The maximum depth of ancestors of a table to display (default = 2)
|
||||
depth: number?,
|
||||
-- An array of keys which should not be visited
|
||||
omit: Types.Array<any>?,
|
||||
-- Whether to use multiple lines (default = false)
|
||||
multiline: boolean?,
|
||||
-- Whether to show the length of any array in front of its content
|
||||
arrayLength: boolean?,
|
||||
-- The maximum length of a line (default = 80)
|
||||
maxLineLength: number?,
|
||||
-- Whether to drop the quotation marks around strings. By default, this is true for table keys
|
||||
noQuotes: boolean?,
|
||||
-- The indent string to use (default = "\t")
|
||||
indent: string?,
|
||||
-- A set of tables which have already been visited and should be referred to by reference
|
||||
visited: Types.Set<Types.Table>?,
|
||||
-- A cycles object returned from `cycles` to aid reference display
|
||||
cycles: cycles.Cycles?,
|
||||
}
|
||||
|
||||
local function indentLines(lines: Types.Array<string>, indent: string)
|
||||
return map(lines, function(line: string)
|
||||
return indent .. line
|
||||
end)
|
||||
end
|
||||
|
||||
local pretty
|
||||
|
||||
|
||||
-- TODO Luau: Improve type inference to a point that this definition does not produce so many type errors
|
||||
-- local function prettyLines(object: any, options: PrettyOptions?): Types.Array<string>
|
||||
local function prettyLines(object: any, options: any): Types.Array<string>
|
||||
options = options or {}
|
||||
if type(object) == "table" then
|
||||
-- A table needs to be serialized recusively
|
||||
-- Construct the options for recursive calls for the table values
|
||||
local valueOptions = assign({
|
||||
visited = {},
|
||||
indent = "\t",
|
||||
depth = 2
|
||||
}, options, {
|
||||
-- Depth is reduced until we shouldn't recurse any more
|
||||
depth = options.depth and options.depth - 1 or nil,
|
||||
cycles = options.cycles or cycles(object, options.depth, {
|
||||
visited = {},
|
||||
refs = {},
|
||||
nextRef = 0,
|
||||
depth = options.depth,
|
||||
omit = options.omit or {}
|
||||
})
|
||||
})
|
||||
if valueOptions.depth == -1 then
|
||||
-- Indicate there is more information available beneath the maximum depth
|
||||
return {"..."}
|
||||
end
|
||||
if valueOptions.visited[object] then
|
||||
-- Indicate this table has been printed already, so print a ref number instead of
|
||||
-- printing it multiple times
|
||||
return {"&" .. valueOptions.cycles.refs[object]}
|
||||
end
|
||||
|
||||
valueOptions.visited[object] = true
|
||||
|
||||
local multiline = valueOptions.multiline
|
||||
local comma = multiline and "," or ", "
|
||||
|
||||
-- If the table appears multiple times in the output, mark it with a ref prefix so it can
|
||||
-- be identified if it crops up later on
|
||||
local ref = valueOptions.cycles.refs[object]
|
||||
local refTag = ref and ("<%s>"):format(ref) or ""
|
||||
local lines = {refTag .. "{"}
|
||||
|
||||
-- Build the options for the recursive call for the table keys
|
||||
local keyOptions = join(valueOptions, {
|
||||
noQuotes = true,
|
||||
multiline = false
|
||||
})
|
||||
|
||||
-- Compact numeric keys into a simpler array style
|
||||
local maxConsecutiveIndex = 0
|
||||
local first = true
|
||||
for index, value in ipairs(object) do
|
||||
if valueOptions.omit and includes(valueOptions.omit, index) then
|
||||
-- Don't include keys which are omitted
|
||||
continue
|
||||
end
|
||||
if first then
|
||||
first = false
|
||||
else
|
||||
lines[#lines] = lines[#lines] .. comma
|
||||
end
|
||||
if valueOptions.multiline then
|
||||
local indendedValue = indentLines(prettyLines(value, valueOptions), valueOptions.indent)
|
||||
append(lines, indendedValue)
|
||||
else
|
||||
lines[#lines] = lines[#lines] .. pretty(value, valueOptions)
|
||||
end
|
||||
maxConsecutiveIndex = index
|
||||
end
|
||||
if #object > 0 and valueOptions.arrayLength then
|
||||
lines[1] = ("#%d %s"):format(#object, lines[1])
|
||||
end
|
||||
-- Ensure keys are printed in order to guarantee consistency
|
||||
local objectKeys = keys(object)
|
||||
sort(objectKeys, function(left, right)
|
||||
if typeof(left) == "number" and typeof(right) == "number" then
|
||||
return left < right
|
||||
else
|
||||
return tostring(left) < tostring(right)
|
||||
end
|
||||
end)
|
||||
for _, key in ipairs(objectKeys) do
|
||||
local value = object[key]
|
||||
-- We printed a key if it's an index e.g. an integer in the range 1..n.
|
||||
if typeof(key) == "number" and key % 1 == 0 and key >= 1 and key <= maxConsecutiveIndex then
|
||||
continue
|
||||
end
|
||||
if valueOptions.omit and includes(valueOptions.omit, key) then
|
||||
-- Don't include keys which are omitted
|
||||
continue
|
||||
end
|
||||
if first then
|
||||
first = false
|
||||
else
|
||||
lines[#lines] = lines[#lines] .. comma
|
||||
end
|
||||
if valueOptions.multiline then
|
||||
local keyLines = prettyLines(key, keyOptions)
|
||||
local indentedKey = indentLines(keyLines, valueOptions.indent)
|
||||
local valueLines = prettyLines(value, valueOptions)
|
||||
local valueTail = slice(valueLines, 2)
|
||||
local indendedValueTail = indentLines(valueTail, valueOptions.indent)
|
||||
-- The last line of the key and first line of the value are concatenated together
|
||||
indentedKey[#indentedKey] = ("%s = %s"):format(indentedKey[#indentedKey], valueLines[1])
|
||||
append(lines, indentedKey)
|
||||
append(lines, indendedValueTail)
|
||||
else
|
||||
lines[#lines] = ("%s%s = %s"):format(lines[#lines], pretty(key, keyOptions), pretty(value, valueOptions))
|
||||
end
|
||||
end
|
||||
if valueOptions.multiline then
|
||||
if first then
|
||||
-- An empty table is just represented as {}
|
||||
lines[#lines] = lines[#lines] .. "}"
|
||||
else
|
||||
insert(lines, "}")
|
||||
end
|
||||
else
|
||||
lines[#lines] = ("%s}"):format(lines[#lines])
|
||||
end
|
||||
return lines
|
||||
elseif type(object) == "string" and not options.noQuotes then
|
||||
return {('"%s"'):format(object)}
|
||||
else
|
||||
return {tostring(object)}
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO Luau: Improve type inference to a point that this definition does not produce so many type errors
|
||||
-- pretty = function(object: any, options: PrettyOptions?): string
|
||||
pretty = function(object: any, options: any): string
|
||||
return concat(prettyLines(object, options), "\n")
|
||||
end
|
||||
|
||||
return pretty
|
||||
@@ -0,0 +1,26 @@
|
||||
--[[
|
||||
Iterate through the elements of the _input_ Array in order 1..n.
|
||||
|
||||
Call the _handler_ for each element, passing the return of the previous call as the first argument.
|
||||
|
||||
The _initial_ value is passed into the first call, and the final value returned by the function.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type ReduceHandler = (any, any, any) -> any
|
||||
|
||||
local function reduce(input: Types.Array<any>, handler: ReduceHandler, initial: any)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.reduce with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.reduce with argument #2 of type {left:?} not {right:?}]])
|
||||
local result = initial
|
||||
for i = 1, #input do
|
||||
result = handler(result, input[i], i)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return reduce
|
||||
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
Reverse the order of the elements in the _input_ Array.
|
||||
]]
|
||||
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
local function reverse(input: Types.Array<any>): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.reverse with argument #1 of type {left:?} not {right:?}]])
|
||||
local output = {}
|
||||
for i = #input, 1, -1 do
|
||||
insert(output, input[i])
|
||||
end
|
||||
return output
|
||||
end
|
||||
return reverse
|
||||
@@ -0,0 +1,26 @@
|
||||
--[[
|
||||
Makes a string of `length` from `input` by repeating characters from `suffix` at the end of the string.
|
||||
|
||||
By default, suffix is " ".
|
||||
|
||||
@example rightPad("toast", 6) --> "toast "
|
||||
@example rightPad("2", 2, "!") --> "2!"
|
||||
@example rightPad("toast", 10, ":)") --> "toast:):):"
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function rightPad(input: string, length: number, suffix: string?): string
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.rightPad with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(length), "number", [[Attempted to call Dash.rightPad with argument #2 of type {left:?} not {right:?}]])
|
||||
|
||||
local definedSuffix = suffix or " "
|
||||
assertEqual(typeof(definedSuffix), "string", [[Attempted to call Dash.rightPad with argument #3 of type {left:?} not {right:?}]])
|
||||
|
||||
local padLength = length - input:len()
|
||||
local remainder = padLength % definedSuffix:len()
|
||||
local repetitions = (padLength - remainder) / definedSuffix:len()
|
||||
return input .. string.rep(suffix or " ", repetitions) .. definedSuffix:sub(1, remainder)
|
||||
end
|
||||
|
||||
return rightPad
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
Returns `true` if the _left_ and _right_ values are equal (by the equality operator) or the
|
||||
inputs are tables, and all their keys are equal.
|
||||
]]
|
||||
local function shallowEqual(left: any, right: any)
|
||||
if left == right then
|
||||
return true
|
||||
end
|
||||
if typeof(left) ~= "table" or typeof(right) ~= "table" or #left ~= #right then
|
||||
return false
|
||||
end
|
||||
if left == nil or right == nil then
|
||||
return false
|
||||
end
|
||||
for key, value in pairs(left) do
|
||||
if right[key] ~= value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for key, value in pairs(right) do
|
||||
if left[key] ~= value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return shallowEqual
|
||||
@@ -0,0 +1,41 @@
|
||||
--[[
|
||||
Return a portion of the _input_ Array starting with the element at the _left_ index and ending
|
||||
with the element at the _right_ index (i.e. an inclusive range)
|
||||
|
||||
If _left_ is not defined, it defaults to 1.
|
||||
If _right_ is not defined, it defaults to the length of the array (i.e. the last element)
|
||||
|
||||
If _left_ is `-n`, the slice starts with the element `n` places from the last one.
|
||||
If _right_ is `-n`, the slice ends with the element `n` places from the last one.
|
||||
|
||||
An empty array is returned if the slice has no or negative length.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
local function slice(input: Types.Array<any>, left: number?, right: number?)
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.slice with argument #1 of type {left:?} not {right:?}]])
|
||||
local output = {}
|
||||
|
||||
-- Default values
|
||||
left = left or 1
|
||||
right = right or #input
|
||||
assertEqual(typeof(left), "number", [[Attempted to call Dash.slice with argument #2 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(right), "number", [[Attempted to call Dash.slice with argument #3 of type {left:?} not {right:?}]])
|
||||
|
||||
if left < 0 then
|
||||
left = #input + left
|
||||
end
|
||||
if right and right < 0 then
|
||||
right = #input + right
|
||||
end
|
||||
for i = left, right do
|
||||
insert(output, input[i])
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
return slice
|
||||
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Iterates through the elements of the _input_ Table in no particular order.
|
||||
|
||||
Calls the _handler_ for each entry and returns `true` if the handler returns truthy for any
|
||||
element which it is called with.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
export type SomeHandler = (any, any) -> boolean
|
||||
|
||||
local function some(input: Types.Map<any, any>, handler: SomeHandler?): boolean
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.some with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(handler), "function", [[Attempted to call Dash.some with argument #2 of type {left:?} not {right:?}]])
|
||||
for key, child in pairs(input) do
|
||||
if handler(child, key) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return some
|
||||
@@ -0,0 +1,33 @@
|
||||
--[[
|
||||
Splits _input_ into parts based on a _pattern_ delimiter and returns a Table of the parts,
|
||||
followed by a Table of the matched delimiters.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
local function splitOn(input: string, pattern: string): Types.Array<string>
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.splitOn with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(pattern), "string", [[Attempted to call Dash.splitOn with argument #2 of type {left:?} not {right:?}]])
|
||||
local parts = {}
|
||||
local delimiters = {}
|
||||
local from = 1
|
||||
if not pattern then
|
||||
for i = 1, #input do
|
||||
insert(parts, input:sub(i, i))
|
||||
end
|
||||
return parts
|
||||
end
|
||||
local delimiterStart, delimiterEnd = input:find(pattern, from)
|
||||
while delimiterStart do
|
||||
insert(delimiters, input:sub(delimiterStart, delimiterEnd))
|
||||
insert(parts, input:sub(from, delimiterStart - 1))
|
||||
from = delimiterEnd + 1
|
||||
delimiterStart, delimiterEnd = input:find(pattern, from)
|
||||
end
|
||||
insert(parts, input:sub(from))
|
||||
return parts, delimiters
|
||||
end
|
||||
|
||||
return splitOn
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
--[[
|
||||
Checks if _input_ starts with the string _start_.
|
||||
@example startsWith("Fun Roblox Games", "Fun") --> true
|
||||
@example startsWith("Chess", "Fun") --> false
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function startsWith(input: string, prefix: string): boolean
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.startsWith with argument #1 of type {left:?} not {right:?}]])
|
||||
assertEqual(typeof(prefix), "string", [[Attempted to call Dash.startsWith with argument #2 of type {left:?} not {right:?}]])
|
||||
return input:sub(1, prefix:len()) == prefix
|
||||
end
|
||||
|
||||
return startsWith
|
||||
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Remove any whitespace at the start and end of the _input_ string.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
|
||||
local function trim(input: string): string
|
||||
assertEqual(typeof(input), "string", [[Attempted to call Dash.trim with argument #1 of type {left:?} not {right:?}]])
|
||||
return input:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
return trim
|
||||
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Returns an Array of the values in the _input_ Map.
|
||||
|
||||
If the input is an Array, ordering is preserved.
|
||||
|
||||
If the input is a Map, elements are returned in an arbitrary order.
|
||||
]]
|
||||
local Dash = script.Parent
|
||||
local Types = require(Dash.Types)
|
||||
local assertEqual = require(Dash.assertEqual)
|
||||
local iterator = require(Dash.iterator)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
-- TODO Luau: Support generic functions
|
||||
local function values(input: Types.Map<any, any>): Types.Array<any>
|
||||
assertEqual(typeof(input), "table", [[Attempted to call Dash.values with argument #1 of type {left:?} not {right:?}]])
|
||||
local result = {}
|
||||
for _, value in iterator(input) do
|
||||
insert(result, value)
|
||||
end
|
||||
return result
|
||||
end
|
||||
return values
|
||||
@@ -0,0 +1,5 @@
|
||||
# Generated by Rotriever. Format subject to change in future releases.
|
||||
name = "roblox/dash"
|
||||
version = "0.1.7"
|
||||
commit = "03aaa4ab36e34e5d4c683c3ec337af80cf70a546"
|
||||
source = "url+https://github.com/roblox/dash"
|
||||
Reference in New Issue
Block a user