diff options
Diffstat (limited to 'builtin/common')
-rw-r--r-- | builtin/common/information_formspecs.lua | 4 | ||||
-rw-r--r-- | builtin/common/misc_helpers.lua | 161 | ||||
-rw-r--r-- | builtin/common/tests/misc_helpers_spec.lua | 98 |
3 files changed, 214 insertions, 49 deletions
diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index 3405263bf..1445a017c 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -22,7 +22,6 @@ local LIST_FORMSPEC_DESCRIPTION = [[ local F = core.formspec_escape local S = core.get_translator("__builtin") -local check_player_privs = core.check_player_privs -- CHAT COMMANDS FORMSPEC @@ -58,10 +57,11 @@ local function build_chatcommands_formspec(name, sel, copy) .. "any entry in the list.").. "\n" .. S("Double-click to copy the entry to the chat history.") + local privs = core.get_player_privs(name) for i, data in ipairs(mod_cmds) do rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," for j, cmds in ipairs(data[2]) do - local has_priv = check_player_privs(name, cmds[2].privs) + local has_priv = privs[cmds[2].privs] rows[#rows + 1] = ("%s,1,%s,%s"):format( has_priv and COLOR_GREEN or COLOR_GRAY, cmds[1], F(cmds[2].params)) diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 542b2040d..f8a905c7b 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -204,7 +204,7 @@ end -------------------------------------------------------------------------------- function string:trim() - return (self:gsub("^%s*(.-)%s*$", "%1")) + return self:match("^%s*(.-)%s*$") end -------------------------------------------------------------------------------- @@ -245,16 +245,16 @@ function math.round(x) return math.ceil(x - 0.5) end - +local formspec_escapes = { + ["\\"] = "\\\\", + ["["] = "\\[", + ["]"] = "\\]", + [";"] = "\\;", + [","] = "\\," +} function core.formspec_escape(text) - if text ~= nil then - text = string.gsub(text,"\\","\\\\") - text = string.gsub(text,"%]","\\]") - text = string.gsub(text,"%[","\\[") - text = string.gsub(text,";","\\;") - text = string.gsub(text,",","\\,") - end - return text + -- Use explicit character set instead of dot here because it doubles the performance + return text and text:gsub("[\\%[%];,]", formspec_escapes) end @@ -265,18 +265,21 @@ function core.wrap_text(text, max_length, as_table) return as_table and {text} or text end - for word in text:gmatch('%S+') do - local cur_length = #table.concat(line, ' ') - if cur_length > 0 and cur_length + #word + 1 >= max_length then + local line_length = 0 + for word in text:gmatch("%S+") do + if line_length > 0 and line_length + #word + 1 >= max_length then -- word wouldn't fit on current line, move to next line - table.insert(result, table.concat(line, ' ')) - line = {} + table.insert(result, table.concat(line, " ")) + line = {word} + line_length = #word + else + table.insert(line, word) + line_length = line_length + 1 + #word end - table.insert(line, word) end - table.insert(result, table.concat(line, ' ')) - return as_table and result or table.concat(result, '\n') + table.insert(result, table.concat(line, " ")) + return as_table and result or table.concat(result, "\n") end -------------------------------------------------------------------------------- @@ -425,54 +428,50 @@ function core.string_to_pos(value) return nil end - local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") - if x and y and z then - x = tonumber(x) - y = tonumber(y) - z = tonumber(z) - return vector.new(x, y, z) - end - x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + value = value:match("^%((.-)%)$") or value -- strip parentheses + + local x, y, z = value:trim():match("^([%d.-]+)[,%s]%s*([%d.-]+)[,%s]%s*([%d.-]+)$") if x and y and z then x = tonumber(x) y = tonumber(y) z = tonumber(z) return vector.new(x, y, z) end + return nil end -------------------------------------------------------------------------------- -function core.string_to_area(value) - local p1, p2 = unpack(value:split(") (")) - if p1 == nil or p2 == nil then - return nil - end - p1 = core.string_to_pos(p1 .. ")") - p2 = core.string_to_pos("(" .. p2) - if p1 == nil or p2 == nil then - return nil +do + local rel_num_cap = "(~?-?%d*%.?%d*)" -- may be overly permissive as this will be tonumber'ed anyways + local num_delim = "[,%s]%s*" + local pattern = "^" .. table.concat({rel_num_cap, rel_num_cap, rel_num_cap}, num_delim) .. "$" + + local function parse_area_string(pos, relative_to) + local pp = {} + pp.x, pp.y, pp.z = pos:trim():match(pattern) + return core.parse_coordinates(pp.x, pp.y, pp.z, relative_to) end - return p1, p2 -end + function core.string_to_area(value, relative_to) + local p1, p2 = value:match("^%((.-)%)%s*%((.-)%)$") + if not p1 then + return + end -local function test_string_to_area() - local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)") - assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2) - assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53) + p1 = parse_area_string(p1, relative_to) + p2 = parse_area_string(p2, relative_to) - p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53") - assert(p1 == nil and p2 == nil) + if p1 == nil or p2 == nil then + return + end - p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53") - assert(p1 == nil and p2 == nil) + return p1, p2 + end end -test_string_to_area() - -------------------------------------------------------------------------------- function table.copy(t, seen) local n = {} @@ -786,6 +785,74 @@ function core.is_nan(number) return number ~= number end +--[[ Helper function for parsing an optionally relative number +of a chat command parameter, using the chat command tilde notation. + +Parameters: +* arg: String snippet containing the number; possible values: + * "<number>": return as number + * "~<number>": return relative_to + <number> + * "~": return relative_to + * Anything else will return `nil` +* relative_to: Number to which the `arg` number might be relative to + +Returns: +A number or `nil`, depending on `arg. + +Examples: +* `core.parse_relative_number("5", 10)` returns 5 +* `core.parse_relative_number("~5", 10)` returns 15 +* `core.parse_relative_number("~", 10)` returns 10 +]] +function core.parse_relative_number(arg, relative_to) + if not arg then + return nil + elseif arg == "~" then + return relative_to + elseif string.sub(arg, 1, 1) == "~" then + local number = tonumber(string.sub(arg, 2)) + if not number then + return nil + end + if core.is_nan(number) or number == math.huge or number == -math.huge then + return nil + end + return relative_to + number + else + local number = tonumber(arg) + if core.is_nan(number) or number == math.huge or number == -math.huge then + return nil + end + return number + end +end + +--[[ Helper function to parse coordinates that might be relative +to another position; supports chat command tilde notation. +Intended to be used in chat command parameter parsing. + +Parameters: +* x, y, z: Parsed x, y, and z coordinates as strings +* relative_to: Position to which to compare the position + +Syntax of x, y and z: +* "<number>": return as number +* "~<number>": return <number> + player position on this axis +* "~": return player position on this axis + +Returns: a vector or nil for invalid input or if player does not exist +]] +function core.parse_coordinates(x, y, z, relative_to) + if not relative_to then + x, y, z = tonumber(x), tonumber(y), tonumber(z) + return x and y and z and { x = x, y = y, z = z } + end + local rx = core.parse_relative_number(x, relative_to.x) + local ry = core.parse_relative_number(y, relative_to.y) + local rz = core.parse_relative_number(z, relative_to.z) + return rx and ry and rz and { x = rx, y = ry, z = rz } +end + function core.inventorycube(img1, img2, img3) img2 = img2 or img1 img3 = img3 or img1 diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index b11236860..7d046d5b7 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -67,9 +67,107 @@ describe("pos", function() end) end) +describe("area parsing", function() + describe("valid inputs", function() + it("accepts absolute numbers", function() + local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2 4 -12.53)") + assert(p1.x == 10 and p1.y == 5 and p1.z == -2) + assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53) + end) + + it("accepts relative numbers", function() + local p1, p2 = core.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10}) + assert(type(p1) == "table" and type(p2) == "table") + assert(p1.x == 1 and p1.y == 2 and p1.z == 3) + assert(p2.x == 15 and p2.y == 5 and p2.z == 10) + + p1, p2 = core.string_to_area("(1 2 3) (~5 ~-5 ~)", {x=10,y=10,z=10}) + assert(type(p1) == "table" and type(p2) == "table") + assert(p1.x == 1 and p1.y == 2 and p1.z == 3) + assert(p2.x == 15 and p2.y == 5 and p2.z == 10) + end) + end) + describe("invalid inputs", function() + it("rejects too few numbers", function() + local p1, p2 = core.string_to_area("(1,1) (1,1,1,1)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + end) + + it("rejects too many numbers", function() + local p1, p2 = core.string_to_area("(1,1,1,1) (1,1,1,1)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + end) + + it("rejects nan & inf", function() + local p1, p2 = core.string_to_area("(1,1,1) (1,1,nan)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(1,1,1) (1,1,~nan)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(1,1,1) (1,~nan,1)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(1,1,1) (1,1,inf)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(1,1,1) (1,1,~inf)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(1,1,1) (1,~inf,1)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)") + assert(p1 == nil and p2 == nil) + end) + + it("rejects words", function() + local p1, p2 = core.string_to_area("bananas", {x=1,y=1,z=1}) + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("bananas", "foobar") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("bananas") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(bananas,bananas,bananas)") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(bananas,bananas,bananas) (bananas,bananas,bananas)") + assert(p1 == nil and p2 == nil) + end) + + it("requires parenthesis & valid numbers", function() + local p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53") + assert(p1 == nil and p2 == nil) + end) + end) +end) + describe("table", function() it("indexof()", function() assert.equal(1, table.indexof({"foo", "bar"}, "foo")) assert.equal(-1, table.indexof({"foo", "bar"}, "baz")) end) end) + +describe("formspec_escape", function() + it("escapes", function() + assert.equal(nil, core.formspec_escape(nil)) + assert.equal("", core.formspec_escape("")) + assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\[")) + end) +end) |