From 7bf577741319957f6a527be02dd41a2faa028df5 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Sun, 9 Mar 2014 14:28:55 +0400 Subject: [PATCH] Added mutually exclusive groups --- rockspecs/argparse-git-1.rockspec | 2 +- spec/mutex_spec.lua | 67 +++++++++++++++++++++++++++++++ spec/usage_spec.lua | 21 ++++++++++ src/argparse.lua | 53 +++++++++++++++++++++++- 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 spec/mutex_spec.lua diff --git a/rockspecs/argparse-git-1.rockspec b/rockspecs/argparse-git-1.rockspec index 416b863..5548c77 100644 --- a/rockspecs/argparse-git-1.rockspec +++ b/rockspecs/argparse-git-1.rockspec @@ -11,7 +11,7 @@ description = { } dependencies = { "lua >= 5.1, < 5.3", - "30log >= 0.8" + "30log >= 0.9.1" } build = { type = "builtin", diff --git a/spec/mutex_spec.lua b/spec/mutex_spec.lua new file mode 100644 index 0000000..3109258 --- /dev/null +++ b/spec/mutex_spec.lua @@ -0,0 +1,67 @@ +local Parser = require "argparse" + +describe("tests related to mutexes", function() + it("handles mutex correctly", function() + local parser = Parser() + parser:mutex( + parser:flag "-q" "--quiet" + :description "Supress logging. ", + parser:flag "-v" "--verbose" + :description "Print additional debug information. " + ) + + local args = parser:parse{"-q"} + assert.same({quiet = true}, args) + args = parser:parse{"-v"} + assert.same({verbose = true}, args) + args = parser:parse{} + assert.same({}, args) + end) + + it("raises an error if mutex is broken", function() + local parser = Parser() + parser:mutex( + parser:flag "-q" "--quiet" + :description "Supress logging. ", + parser:flag "-v" "--verbose" + :description "Print additional debug information. " + ) + + assert.has_error(function() parser:parse{"-qv"} end, "option '-v' can not be used together with option '-q'") + assert.has_error(function() parser:parse{"-v", "--quiet"} end, "option '--quiet' can not be used together with option '-v'") + end) + + it("handles multiple mutexes", function() + local parser = Parser() + parser:mutex( + parser:flag "-q" "--quiet", + parser:flag "-v" "--verbose" + ) + parser:mutex( + parser:flag "-l" "--local", + parser:option "-f" "--from" + ) + + local args = parser:parse{"-qq", "-fTHERE"} + assert.same({quiet = true, from = "THERE"}, args) + args = parser:parse{"-vl"} + assert.same({verbose = true, ["local"] = true}, args) + end) + + it("handles mutexes in commands", function() + local parser = Parser() + parser:mutex( + parser:flag "-q" "--quiet", + parser:flag "-v" "--verbose" + ) + local install = parser:command "install" + install:mutex( + install:flag "-l" "--local", + install:option "-f" "--from" + ) + + local args = parser:parse{"install", "-l"} + assert.same({install = true, ["local"] = true}, args) + assert.has_error(function() parser:parse{"install", "-qlv"} end, "option '-v' can not be used together with option '-q'") + end) +end) diff --git a/spec/usage_spec.lua b/spec/usage_spec.lua index c4faa1b..dbffcbe 100644 --- a/spec/usage_spec.lua +++ b/spec/usage_spec.lua @@ -158,4 +158,25 @@ describe("tests related to usage message generation", function() ) end) end) + + it("creates correct usage message for mutexes", function() + local parser = Parser "foo" + :add_help(false) + parser:mutex( + parser:flag "-q" "--quiet", + parser:flag "-v" "--verbose", + parser:flag "-i" "--interactive" + ) + parser:mutex( + parser:flag "-l" "--local", + parser:option "-f" "--from" + ) + parser:option "--yet-another-option" + + assert.equal(table.concat({ + "Usage: foo ([-q] | [-v] | [-i]) ([-l] | [-f ])", + " [--yet-another-option ]" + }, "\r\n"), parser:get_usage() + ) + end) end) diff --git a/src/argparse.lua b/src/argparse.lua index 29150c7..cb73826 100644 --- a/src/argparse.lua +++ b/src/argparse.lua @@ -157,6 +157,7 @@ do -- Create classes with setters _arguments = {}, _options = {}, _commands = {}, + _mutexes = {}, _require_command = true }, { name = typecheck.string "name", @@ -424,6 +425,17 @@ function Parser:command(...) return command end +function Parser:mutex(...) + local options = {...} + + for i, option in ipairs(options) do + assert(getmetatable(option) == Option, ("bad argument #%d to 'mutex' (Option expected)"):format(i)) + end + + table.insert(self._mutexes, options) + return self +end + local max_usage_width = 70 local usage_welcome = "Usage: " @@ -442,9 +454,25 @@ function Parser:get_usage() end end + -- set of mentioned elements + local used = {} + + for _, mutex in ipairs(self._mutexes) do + local buf = {} + + for _, option in ipairs(mutex) do + table.insert(buf, option:_get_usage()) + used[option] = true + end + + add("(" .. table.concat(buf, " | ") .. ")") + end + for _, elements in ipairs{self._options, self._arguments} do for _, element in ipairs(elements) do - add(element:_get_usage()) + if not used[element] then + add(element:_get_usage()) + end end end @@ -583,6 +611,8 @@ function Parser:_parse(args, errhandler) local options = {} local arguments = {} local commands + local option_mutexes = {} + local used_mutexes = {} local opt_context = {} local com_context local result = {} @@ -726,6 +756,16 @@ function Parser:_parse(args, errhandler) invocations[option] = 0 end + for _, mutex in ipairs(parser._mutexes) do + for _, option in ipairs(mutex) do + if not option_mutexes[option] then + option_mutexes[option] = {mutex} + else + table.insert(option_mutexes[option], mutex) + end + end + end + for _, argument in ipairs(parser._arguments) do table.insert(arguments, argument) invocations[argument] = 0 @@ -784,6 +824,17 @@ function Parser:_parse(args, errhandler) end cur_option = opt_context[data] + + if option_mutexes[cur_option] then + for _, mutex in ipairs(option_mutexes[cur_option]) do + if used_mutexes[mutex] and used_mutexes[mutex] ~= cur_option then + error_("option '%s' can not be used together with option '%s'", data, used_mutexes[mutex]._name) + else + used_mutexes[mutex] = cur_option + end + end + end + do_action(cur_option) invoke(cur_option) end