add gs
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
# Generated by Rotriever. Format subject to change in future releases.
|
||||
name = "roblox/string-utilities"
|
||||
version = "1.0.0"
|
||||
commit = "7cbef7885ca023d71dd93f02ffa32cbda65faa9c"
|
||||
source = "url+https://github.com/roblox/string-utilities"
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
--!nocheck
|
||||
local RunService = game:GetService("RunService")
|
||||
local StringSplit = require(script.Parent.StringSplit)
|
||||
|
||||
function assertIsType(value, expectedType, name)
|
||||
if RunService:IsStudio() or _G.__TESTEZ_RUNNING_TEST__ then
|
||||
assert(
|
||||
typeof(value) == expectedType,
|
||||
string.format("expects %s to be a %s! it was: %s", name, expectedType, typeof(value))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function urlDecode(input)
|
||||
assertIsType(input, "string", "input")
|
||||
local decoded = string.gsub(input, '%%(%x%x)', function(charCode)
|
||||
return string.char(tonumber(charCode, 16))
|
||||
end)
|
||||
decoded = string.gsub(decoded, "+", " ")
|
||||
return decoded;
|
||||
end
|
||||
|
||||
--[[
|
||||
Parses a query string into a lua table.
|
||||
* supports both "&"" and ";" as separator (and "=" separates names/values)
|
||||
* empty names or values are returned as "" (eg "k1=&=v2&k3")
|
||||
* completely empty pairs are ignored (eg "k1=v1&&k3=v3")
|
||||
* url decodes all names and values
|
||||
* supports multiple values
|
||||
* by default, all values are returned in tables
|
||||
* if listKeyMapper (name => listKey) is provided:
|
||||
* only the last value for a name is returned as that key (ie name)
|
||||
* the list of values is returned at the key provided by listKeyMapper
|
||||
* listKeyMapper can return the same input key (ie name), or nil (same effect)
|
||||
]]
|
||||
local function ParseQuery(input, listKeyMapper)
|
||||
if input ~= nil then
|
||||
assertIsType(input, "string", "input")
|
||||
end
|
||||
if listKeyMapper ~= nil then
|
||||
assertIsType(listKeyMapper, "function", "listKeyMapper")
|
||||
end
|
||||
|
||||
local useListKeys = type(listKeyMapper) == "function"
|
||||
local parsed = {}
|
||||
if input and #input > 0 then
|
||||
local items = StringSplit(input, "[&;]")
|
||||
for _, item in ipairs(items) do
|
||||
if item and #item > 0 then
|
||||
local key, value = unpack(string.split(item, "="))
|
||||
if value == nil then
|
||||
value = ""
|
||||
end
|
||||
key = urlDecode(key)
|
||||
value = urlDecode(value)
|
||||
if useListKeys then
|
||||
if parsed[key] ~= nil then
|
||||
local listKey = listKeyMapper(key)
|
||||
if listKey == nil then
|
||||
listKey = key
|
||||
end
|
||||
if type(parsed[listKey]) ~= "table" then
|
||||
parsed[listKey] = { parsed[key] }
|
||||
end
|
||||
table.insert(parsed[listKey], value)
|
||||
if key ~= listKey then
|
||||
parsed[key] = value
|
||||
end
|
||||
else
|
||||
parsed[key] = value
|
||||
end
|
||||
else
|
||||
if parsed[key] == nil then
|
||||
parsed[key] = {}
|
||||
end
|
||||
table.insert(parsed[key], value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return parsed
|
||||
end
|
||||
|
||||
return ParseQuery
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
return function()
|
||||
local ParseQuery = require(script.Parent.ParseQuery)
|
||||
|
||||
describe("ParseQuery normal usage", function()
|
||||
it("should parse a normal query string", function()
|
||||
local parsed = ParseQuery("name1=value1&name2=value2")
|
||||
|
||||
expect(parsed.name1[1]).to.equal("value1")
|
||||
expect(parsed.name2[1]).to.equal("value2")
|
||||
end)
|
||||
|
||||
it("should parse multiple values for the same name", function()
|
||||
local parsed = ParseQuery("name1=value1&name1=value2")
|
||||
|
||||
expect(#parsed.name1).to.equal(2)
|
||||
expect(parsed.name1[1]).to.equal("value1")
|
||||
expect(parsed.name1[2]).to.equal("value2")
|
||||
end)
|
||||
|
||||
it("should allow custom keys for multiple values", function()
|
||||
local parsed = ParseQuery("name1=value1&name2=value2&name2=value3", function(key)
|
||||
return "list_of_" .. key
|
||||
end)
|
||||
|
||||
expect(parsed.name1).to.equal("value1")
|
||||
expect(parsed.name2).to.equal("value3")
|
||||
|
||||
expect(#parsed.list_of_name2).to.equal(2)
|
||||
expect(parsed.list_of_name2[1]).to.equal("value2")
|
||||
expect(parsed.list_of_name2[2]).to.equal("value3")
|
||||
end)
|
||||
|
||||
it("should url decode names and values", function()
|
||||
local parsed = ParseQuery("name1=value+1&%5Fname2=value2")
|
||||
|
||||
expect(parsed.name1[1]).to.equal("value 1")
|
||||
expect(parsed._name2[1]).to.equal("value2")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ParseQuery edge cases", function()
|
||||
it("should return an empty table on empty or nil input", function()
|
||||
local parsed1 = ParseQuery("")
|
||||
local parsed2 = ParseQuery(nil)
|
||||
|
||||
expect(#parsed1).to.equal(0)
|
||||
expect(#parsed2).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should support listKeyMapper returning key or nil", function()
|
||||
local parsed = ParseQuery("name1=value1&name2=value2&name2=value3", function()end)
|
||||
|
||||
expect(parsed.name1).to.equal("value1")
|
||||
expect(#parsed.name2).to.equal(2)
|
||||
expect(parsed.name2[1]).to.equal("value2")
|
||||
expect(parsed.name2[2]).to.equal("value3")
|
||||
end)
|
||||
|
||||
it("should use empty strings for unavailable values", function()
|
||||
local parsed = ParseQuery("name1=&=value2&name3", function()end)
|
||||
|
||||
expect(parsed.name1).to.equal("")
|
||||
expect(parsed[""]).to.equal("value2")
|
||||
expect(parsed.name3).to.equal("")
|
||||
end)
|
||||
|
||||
it("should support `;` as separator", function()
|
||||
local parsed = ParseQuery("name1=value1;name2=value2", function()end)
|
||||
|
||||
expect(parsed.name1).to.equal("value1")
|
||||
expect(parsed.name2).to.equal("value2")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
-- Performs multiple replacements on the given string.
|
||||
-- replacements is a table where the keys are patterns to be replaced, and the
|
||||
-- values are the replacements to be made. For example:
|
||||
-- StringReplaceAll(
|
||||
-- "key1 key2 key3",
|
||||
-- {["%w+1"] = "value1", ["%w+2"] = "value2"}
|
||||
-- )
|
||||
-- becomes "value1 value2 key3".
|
||||
return function(str, replacements)
|
||||
if type(str) ~= "string" then
|
||||
return ""
|
||||
end
|
||||
|
||||
if type(replacements) ~= "table" then
|
||||
return str
|
||||
end
|
||||
|
||||
local result = str
|
||||
for piiStr, replaceStr in pairs(replacements) do
|
||||
if type(piiStr) == "string" and type(replaceStr) == "string" then
|
||||
result = string.gsub(result, piiStr, replaceStr)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
return function()
|
||||
local StringReplaceAll = require(script.Parent.StringReplaceAll)
|
||||
|
||||
it("should return empty string if str is not a string", function()
|
||||
local result = StringReplaceAll(1, { "a" })
|
||||
expect(result).to.equal("")
|
||||
|
||||
result = StringReplaceAll({ 1, 2, 3 }, { "a" })
|
||||
expect(result).to.equal("")
|
||||
|
||||
result = StringReplaceAll(nil, { "a" })
|
||||
expect(result).to.equal("")
|
||||
end)
|
||||
|
||||
it("should return original string if replacements is not a table", function()
|
||||
local result = StringReplaceAll("abc", "a")
|
||||
expect(result).to.equal("abc")
|
||||
|
||||
result = StringReplaceAll("abc", 1)
|
||||
expect(result).to.equal("abc")
|
||||
|
||||
result = StringReplaceAll("abc", nil)
|
||||
expect(result).to.equal("abc")
|
||||
end)
|
||||
|
||||
it("should replace a PII string if provided", function()
|
||||
local result = StringReplaceAll("abc", { a = "" })
|
||||
expect(result).to.equal("bc")
|
||||
|
||||
result = StringReplaceAll("abc", { b = "" })
|
||||
expect(result).to.equal("ac")
|
||||
|
||||
result = StringReplaceAll("abc-123-a2c", { ["2"] = "" })
|
||||
expect(result).to.equal("abc-13-ac")
|
||||
|
||||
result = StringReplaceAll("abc-123-a2c", { a = "A" })
|
||||
expect(result).to.equal("Abc-123-A2c")
|
||||
|
||||
-- special character
|
||||
result = StringReplaceAll("hello, said Noob_123", { ["Noob_123"] = "Username" })
|
||||
expect(result).to.equal("hello, said Username")
|
||||
end)
|
||||
|
||||
it("should replace all PII strings provided", function()
|
||||
local result = StringReplaceAll("abc", { a = "A", c = "d"})
|
||||
expect(result).to.equal("Abd")
|
||||
|
||||
result = StringReplaceAll("abc", { a = "A", bc = "" })
|
||||
expect(result).to.equal("A")
|
||||
|
||||
result = StringReplaceAll("abc-123-a23", { abc = "user", ["123"] = "id" })
|
||||
expect(result).to.equal("user-id-a23")
|
||||
end)
|
||||
|
||||
it("should return original string if replacements is an empty table or no matches are found", function()
|
||||
local result = StringReplaceAll("abc", {})
|
||||
expect(result).to.equal("abc")
|
||||
|
||||
result = StringReplaceAll("abc", { d = "e" })
|
||||
expect(result).to.equal("abc")
|
||||
|
||||
result = StringReplaceAll("abc", { d = "e", e = "f" })
|
||||
expect(result).to.equal("abc")
|
||||
end)
|
||||
|
||||
it("should ignore PIIs if they are not strings", function()
|
||||
local result = StringReplaceAll("abc", { a = 1 })
|
||||
expect(result).to.equal("abc")
|
||||
|
||||
result = StringReplaceAll("abc", { a = 1, bc = "" })
|
||||
expect(result).to.equal("a")
|
||||
end)
|
||||
|
||||
it("should match the example given in the doc string", function()
|
||||
local result = StringReplaceAll("key1 key2 key3", {["%w+1"] = "value1", ["%w+2"] = "value2"})
|
||||
expect(result).to.equal("value1 value2 key3")
|
||||
end)
|
||||
end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
-- Split the input string according to the separator pattern into at most limit
|
||||
-- pieces. If the separator is nil, it defaults to consecutive whitespace (%s+).
|
||||
-- If the limit isn't defined, it will split the string into as many pieces as
|
||||
-- it finds.
|
||||
-- Note: There is now a string.split function built into Luau though it isn't an
|
||||
-- exact replacement for this.
|
||||
local function StringSplit(input, separator, limit)
|
||||
if #input == 0 then
|
||||
if string.find(input, separator) then
|
||||
return {}
|
||||
else
|
||||
return {""}
|
||||
end
|
||||
end
|
||||
if not limit then
|
||||
limit = -1
|
||||
end
|
||||
if limit == 1 then
|
||||
return {input}
|
||||
end
|
||||
if not separator then
|
||||
separator = "%s+"
|
||||
end
|
||||
local start, stop = string.find(input, separator)
|
||||
if not start then
|
||||
return {input}
|
||||
end
|
||||
-- special case, delimiter resolved to ""
|
||||
if stop < 1 then
|
||||
start, stop = string.find(string.sub(input, 2), separator)
|
||||
start = start + 1
|
||||
stop = stop + 1
|
||||
end
|
||||
local first = string.sub(input, 1, start - 1)
|
||||
local rest = string.sub(input, stop + 1)
|
||||
-- special case, non empty pattern found at the end
|
||||
if #rest == 0 and stop >= start then
|
||||
return {first, rest}
|
||||
end
|
||||
local items = StringSplit(rest, separator, limit - 1)
|
||||
table.insert(items, 1, first)
|
||||
return items
|
||||
end
|
||||
|
||||
return StringSplit
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
return function()
|
||||
local StringSplit = require(script.Parent.StringSplit)
|
||||
|
||||
describe("Normal usage", function()
|
||||
|
||||
it("should split a string by a one character separator", function()
|
||||
local str = "Roblox Powering Imagination"
|
||||
local words = StringSplit(str, " ")
|
||||
|
||||
expect(#words).to.equal(3)
|
||||
expect(words[1]).to.equal("Roblox")
|
||||
expect(words[2]).to.equal("Powering")
|
||||
expect(words[3]).to.equal("Imagination")
|
||||
end)
|
||||
|
||||
it("should split a string by a complex regex", function()
|
||||
local str = "https://corp.roblox.com/technology"
|
||||
local words = StringSplit(str, "[^a-z]+")
|
||||
|
||||
expect(#words).to.equal(5)
|
||||
expect(words[1]).to.equal("https")
|
||||
expect(words[2]).to.equal("corp")
|
||||
expect(words[3]).to.equal("roblox")
|
||||
expect(words[4]).to.equal("com")
|
||||
expect(words[5]).to.equal("technology")
|
||||
end)
|
||||
|
||||
it("should split on blank spaces by default", function()
|
||||
local str = "together through\n\tplay"
|
||||
local words = StringSplit(str)
|
||||
|
||||
expect(#words).to.equal(3)
|
||||
expect(words[1]).to.equal("together")
|
||||
expect(words[2]).to.equal("through")
|
||||
expect(words[3]).to.equal("play")
|
||||
end)
|
||||
|
||||
it("should not exceed the provided limit", function()
|
||||
local str = "Modules/Common/StringUtilities/StringSplit"
|
||||
local words = StringSplit(str, "/", 3)
|
||||
|
||||
expect(#words).to.equal(3)
|
||||
expect(words[1]).to.equal("Modules")
|
||||
expect(words[2]).to.equal("Common")
|
||||
expect(words[3]).to.equal("StringUtilities/StringSplit")
|
||||
end)
|
||||
|
||||
it("should give en empty string on leading, repeated and trailing separators", function()
|
||||
local str = "/var//www/"
|
||||
local words = StringSplit(str, "/")
|
||||
|
||||
expect(#words).to.equal(5)
|
||||
expect(words[1]).to.equal("")
|
||||
expect(words[2]).to.equal("var")
|
||||
expect(words[3]).to.equal("")
|
||||
expect(words[4]).to.equal("www")
|
||||
expect(words[5]).to.equal("")
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("Edge case:", function()
|
||||
|
||||
it("an empty string results in one empty result", function()
|
||||
local str = ""
|
||||
local words = StringSplit(str, "/")
|
||||
|
||||
expect(#words).to.equal(1)
|
||||
expect(words[1]).to.equal("")
|
||||
end)
|
||||
|
||||
it("an empty separator splits to characters, no leading/trailing empty strings", function()
|
||||
local str = "lua"
|
||||
local words = StringSplit(str, "")
|
||||
|
||||
expect(#words).to.equal(3)
|
||||
expect(words[1]).to.equal("l")
|
||||
expect(words[2]).to.equal("u")
|
||||
expect(words[3]).to.equal("a")
|
||||
end)
|
||||
|
||||
it("empty string AND separator should get an empty array", function()
|
||||
local str = ""
|
||||
local words = StringSplit(str, "")
|
||||
|
||||
expect(#words).to.equal(0)
|
||||
end)
|
||||
|
||||
it("regex separators can resolve to empty and non empty in same operation", function()
|
||||
local str = "//r#blox"
|
||||
local words = StringSplit(str, "[^a-z]*")
|
||||
|
||||
expect(#words).to.equal(6)
|
||||
-- pattern resolved to "//", creating a leading empty string
|
||||
expect(words[1]).to.equal("")
|
||||
-- pattern resolved to "#"
|
||||
expect(words[2]).to.equal("r")
|
||||
-- pattern resolved to "", splitting by character
|
||||
expect(words[3]).to.equal("b")
|
||||
expect(words[4]).to.equal("l")
|
||||
expect(words[5]).to.equal("o")
|
||||
expect(words[6]).to.equal("x")
|
||||
-- pattern resolved to "", NO trailing empty string
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
-- Trim the given set of characters from both ends of the input string. `sides`
|
||||
-- can be set to {left = true} or {right = true} to trim from just one side.
|
||||
local function StringTrim(input, chars, sides)
|
||||
if not chars then
|
||||
chars = "%s"
|
||||
end
|
||||
if #chars == 0 then
|
||||
return input
|
||||
end
|
||||
if not sides then
|
||||
sides = {
|
||||
left = true,
|
||||
right = true,
|
||||
}
|
||||
end
|
||||
local trimmed = input
|
||||
if sides.left then
|
||||
local start = string.find(trimmed, "[^" .. chars .. "]")
|
||||
if not start then
|
||||
return ""
|
||||
end
|
||||
trimmed = string.sub(trimmed, start)
|
||||
end
|
||||
if sides.right then
|
||||
local stop = string.find(trimmed, "[" .. chars .. "]+$")
|
||||
if not stop then
|
||||
return trimmed
|
||||
end
|
||||
trimmed = string.sub(trimmed, 1, stop-1)
|
||||
end
|
||||
return trimmed
|
||||
end
|
||||
|
||||
return StringTrim
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
return function()
|
||||
local StringTrim = require(script.Parent.StringTrim)
|
||||
|
||||
describe("Normal usage", function()
|
||||
|
||||
it("should trim the specified character", function()
|
||||
local str = "/Modules/Common/StringUtilities/"
|
||||
local trimmed = StringTrim(str, "/")
|
||||
|
||||
expect(trimmed).to.equal("Modules/Common/StringUtilities")
|
||||
end)
|
||||
|
||||
it("should accept multiple charcters", function()
|
||||
local str = "(Roblox Powering Imagination){}"
|
||||
local trimmed = StringTrim(str, "(){}")
|
||||
|
||||
expect(trimmed).to.equal("Roblox Powering Imagination")
|
||||
end)
|
||||
|
||||
it("should be able to trim only on the right side", function()
|
||||
local str = "(Roblox Powering Imagination){}"
|
||||
local trimmed = StringTrim(str, "(){}", {right = true})
|
||||
|
||||
expect(trimmed).to.equal("(Roblox Powering Imagination")
|
||||
end)
|
||||
|
||||
it("should be able to trim only on the left side", function()
|
||||
local str = "(Roblox Powering Imagination){}"
|
||||
local trimmed = StringTrim(str, "(){}", {left = true})
|
||||
|
||||
expect(trimmed).to.equal("Roblox Powering Imagination){}")
|
||||
end)
|
||||
|
||||
it("should default to trimming blanks on both sides", function()
|
||||
local str = "\tRoblox Powering Imagination \n"
|
||||
local trimmed = StringTrim(str)
|
||||
|
||||
expect(trimmed).to.equal("Roblox Powering Imagination")
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("Edge case:", function()
|
||||
|
||||
it("an empty character list is a no-op", function()
|
||||
local str = " Roblox Powering Imagination "
|
||||
local trimmed = StringTrim(str, "")
|
||||
|
||||
expect(trimmed).to.equal(str)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
return {
|
||||
ParseQuery = require(script.ParseQuery),
|
||||
StringReplaceAll = require(script.StringReplaceAll),
|
||||
StringSplit = require(script.StringSplit),
|
||||
StringTrim = require(script.StringTrim),
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
return function()
|
||||
it("should import without error", function()
|
||||
require(script.Parent)
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user