From 0fa1e5fec46e9825e34a1776bbfc375e6ca36c0b Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 13 May 2012 22:57:59 +0100 Subject: Ruleset inclusion support --- lib/lace/builtin.lua | 106 +++++++++++++++++++++++----- test/test-lace.builtin.lua | 168 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 252 insertions(+), 22 deletions(-) diff --git a/lib/lace/builtin.lua b/lib/lace/builtin.lua index 15b88af..5978bd0 100644 --- a/lib/lace/builtin.lua +++ b/lib/lace/builtin.lua @@ -15,6 +15,30 @@ local function compiler() return require "lace.compiler" end +local function run_conditions(exec_context, cond) + for i = 1, #cond do + local name = cond[i] + local invert = false + if name:sub(1,1) == "!" then + invert = true + name = name:sub(2) + end + local res, msg = engine.test(exec_context, name) + if res == nil then + return nil, msg + end + if invert then + res = not res + end + if not res then + -- condition failed + return false + end + end + -- conditions passed + return true +end + --[ Allow and Deny ]------------------------------------------------ local unconditional_result, last_result @@ -32,24 +56,13 @@ local function get_set_last_result(newv) end local function _do_return(exec_context, result, reason, cond) - for i = 1, #cond do - local name = cond[i] - local invert = false - if name:sub(1,1) == "!" then - invert = true - name = name:sub(2) - end - local res, msg = engine.test(exec_context, name) - if res == nil then - return nil, msg - end - if invert then - res = not res - end - if not res then - -- condition failed, return true to continue execution - return true - end + local pass, msg = run_conditions(exec_context, cond) + if pass == nil then + -- Pass errors + return nil, msg + elseif pass == false then + -- Conditions failed, return true to continue execution + return true end return result, reason end @@ -153,6 +166,63 @@ end builtin.def = builtin.define +--[ Inclusion of rulesets ]------------------------------------------- + +local function _do_include(exec_context, ruleset, conds) + local pass, msg = run_conditions(exec_context, conds) + if pass == nil then + -- Pass errors + return nil, msg + elseif pass == false then + -- Conditions failed, return true to continue execution + return true + end + -- Essentially we run the ruleset and return its values + local result, msg = engine.internal_run(ruleset, exec_context) + if result == "" then + return true + end + return result, msg +end + +function builtin.include(comp_context, cmd, file, ...) + local safe_if_not_present = cmd:sub(-1) == "?" + + local conds = {...} + + if type(file) ~= "string" then + return compiler().error("No file named for inclusion") + end + + local loader = compiler().internal_loader(comp_context) + local real, content = loader(comp_context, file) + + if not real then + -- Could not find the file + if safe_if_not_present then + -- Include file was not present, just return an empty command + return { + fn = function() return true end, + args = {} + } + end + -- Otherwise, propagate the error + return real, content + end + + -- Okay, the file is present, let's parse it. + local ruleset, msg = compiler().internal_compile(comp_context, real, content, true) + if type(ruleset) ~= "table" then + return compiler().error(msg) + end + + -- Okay, we parsed, so build the runtime + return { + fn = _do_include, + args = { ruleset, conds } + } +end + return { commands = builtin, get_set_last_unconditional_result = get_set_last_unconditional_result, diff --git a/test/test-lace.builtin.lua b/test/test-lace.builtin.lua index a20f4cd..59d36cb 100644 --- a/test/test-lace.builtin.lua +++ b/test/test-lace.builtin.lua @@ -274,7 +274,6 @@ function suite.run_allow_deny_with_condition_present_failing() } local ectx = {[".lace"] = {defs = { cheese = _cheesetab }}} local ok, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) - print(ok, msg) assert(ok == true, "Running a conditional allow where the conditions fail should return continuation") end @@ -290,7 +289,6 @@ function suite.run_allow_deny_with_condition_present_passing() } local ectx = {[".lace"] = {defs = { cheese = _cheesetab }}} local ok, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) - print(ok, msg) assert(ok == "allow", "Running a conditional allow where the conditions pass should return allow") assert(msg == "because", "Resulting in the reason given") end @@ -306,7 +304,6 @@ function suite.run_allow_deny_with_inverted_condition_present_failing() } local ectx = {[".lace"] = {defs = { cheese = _cheesetab }}} local ok, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) - print(ok, msg) assert(ok == true, "Running a conditional allow where the conditions fail should return continuation") end @@ -322,11 +319,174 @@ function suite.run_allow_deny_with_inverted_condition_present_passing() } local ectx = {[".lace"] = {defs = { cheese = _cheesetab }}} local ok, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) - print(ok, msg) assert(ok == "allow", "Running a conditional allow where the conditions pass should return allow") assert(msg == "because", "Resulting in the reason given") end +function suite.compile_include_statement_noname() + local compctx = {[".lace"] = {}} + local cmdtab, msg = builtin.commands.include(compctx, "include") + assert(cmdtab == false, "Internal errors should return false") + assert(type(msg) == "table", "Internal errors should return tables") + assert(type(msg.msg) == "string", "Internal errors should have string messages") + assert(msg.msg:match("file name"), "Expected error should mention a lack of name") +end + +function suite.compile_include_statement_noloader() + local compctx = {[".lace"] = {}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish") + assert(cmdtab == false, "Internal errors should return false") + assert(type(msg) == "table", "Internal errors should return tables") + assert(type(msg.msg) == "string", "Internal errors should have string messages") + assert(msg.msg:match("fish"), "Expected error should mention a name of unloadable content") +end + +function suite.compile_safe_include_statement_noloader() + local compctx = {[".lace"] = {}} + local cmdtab, msg = builtin.commands.include(compctx, "include?", "fish") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") +end + +function suite.compile_include_statement_withloader() + local function _loader(cctx, name) + return name, "-- nada\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") +end + +function suite.compile_include_statement_withloader_badcode() + local function _loader(cctx, name) + return name, "nada\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish") + assert(cmdtab == false, "Internal errors should return false") + assert(type(msg) == "table", "Internal errors should return tables") + assert(type(msg.msg) == "string", "Internal errors should have string messages") + assert(msg.msg:match("nada"), "Expected error should mention the bad command name") +end + +function suite.run_include_statement_withloader_good_code() + local function _loader(cctx, name) + return name, "--nada\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = {} + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == true, "Should be continuing as-is") +end + +function suite.run_include_statement_withloader_good_code_allow() + local function _loader(cctx, name) + return name, "allow because\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = {} + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == "allow", "Should pass result") + assert(msg == "because", "Should pass reason") +end + +function suite.run_cond_include_present_erroring() + local function _loader(cctx, name) + return name, "allow because\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish", "cheese") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = {} + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == nil, "Should fail gracefully") + assert(msg.msg:match("cheese"), "Error should mention undefined variable") +end + +function suite.run_cond_include_present_erroring() + local function _loader(cctx, name) + return name, "allow because\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish", "cheese") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = { + [".lace"] = { + defs = { + cheese = { + fn = function() return nil, { msg = "FAIL" } end, + args = {} + } + } + } + } + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == nil, "Should fail gracefully") + assert(msg.msg:match("FAIL"), "Error should mention undefined variable") +end + +function suite.run_cond_include_present_failing() + local function _loader(cctx, name) + return name, "allow because\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish", "cheese") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = { + [".lace"] = { + defs = { + cheese = { + fn = function() return false end, + args = {} + } + } + } + } + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == true, "Should continue blithely") +end + +function suite.run_cond_include_present_passing() + local function _loader(cctx, name) + return name, "allow because\n" + end + local compctx = {[".lace"] = {loader=_loader}} + local cmdtab, msg = builtin.commands.include(compctx, "include", "fish", "cheese") + assert(type(cmdtab) == "table", "Commands should be tables") + assert(type(cmdtab.fn) == "function", "With a function") + assert(type(cmdtab.args) == "table", "and arguments") + local ectx = { + [".lace"] = { + defs = { + cheese = { + fn = function() return true end, + args = {} + } + } + } + } + local result, msg = cmdtab.fn(ectx, unpack(cmdtab.args)) + assert(result == "allow", "Should propagate success") + assert(msg == "because", "Should propagate reason") +end + local count_ok = 0 for _, testname in ipairs(testnames) do print("Run: " .. testname) -- cgit v1.2.1