From 404ec5213af002af27a7d68c6eee33369436907a Mon Sep 17 00:00:00 2001 From: mpeterv Date: Sun, 26 Jan 2014 15:20:42 +0400 Subject: [PATCH] Added help message generation; Improved optional arguments handling. --- spec/arguments_spec.lua | 9 ++++ spec/commands_spec.lua | 1 + spec/options_spec.lua | 1 + spec/usage_spec.lua | 9 ++++ src/argparse.lua | 99 +++++++++++++++++++++++++++++++++++++++-- 5 files changed, 116 insertions(+), 3 deletions(-) diff --git a/spec/arguments_spec.lua b/spec/arguments_spec.lua index 9663e6d..1c40fd0 100644 --- a/spec/arguments_spec.lua +++ b/spec/arguments_spec.lua @@ -15,6 +15,14 @@ describe("tests related to positional arguments", function() assert.same({foo = "bar"}, args) end) + it("handles optional argument correctly", function() + local parser = argparse.parser() + parser:argument "foo" + :args "?" + local args = parser:parse({"bar"}) + assert.same({foo = "bar"}, args) + end) + it("handles several arguments correctly", function() local parser = argparse.parser() parser:argument "foo1" @@ -86,6 +94,7 @@ describe("tests related to positional arguments", function() it("handles sudden option correctly", function() local parser = argparse.parser() + :add_help(false) parser:argument "foo" assert.has_error(function() parser:parse{"-q"} end, "unknown option '-q'") diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index 72d321e..b32ed95 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -13,6 +13,7 @@ describe("tests related to commands", function() it("switches context properly", function() local parser = argparse.parser "name" + :add_help(false) local install = parser:command "install" install:flag "-q" "--quiet" diff --git a/spec/options_spec.lua b/spec/options_spec.lua index 99542c0..8a63dbe 100644 --- a/spec/options_spec.lua +++ b/spec/options_spec.lua @@ -185,6 +185,7 @@ describe("tests related to options", function() it("handles unknown options correctly", function() local parser = argparse.parser() + :add_help(false) assert.has_error(function() parser:parse{"--server"} end, "unknown option '--server'") assert.has_error(function() parser:parse{"--server=localhost"} end, "unknown option '--server'") assert.has_error(function() parser:parse{"-s"} end, "unknown option '-s'") diff --git a/spec/usage_spec.lua b/spec/usage_spec.lua index c2b7967..58c5b6f 100644 --- a/spec/usage_spec.lua +++ b/spec/usage_spec.lua @@ -3,11 +3,13 @@ local argparse = require "argparse" describe("tests related to usage message generation", function() it("creates correct usage message for empty parser", function() local parser = argparse.parser "foo" + :add_help(false) assert.equal(parser:prepare():get_usage(), "Usage: foo") end) it("creates correct usage message for arguments", function() local parser = argparse.parser "foo" + :add_help(false) parser:argument "first" parser:argument "second-and-third" :args "2" @@ -24,6 +26,7 @@ describe("tests related to usage message generation", function() it("creates correct usage message for options", function() local parser = argparse.parser "foo" + :add_help(false) parser:flag "-q" "--quiet" parser:option "--from" :count "1" @@ -38,6 +41,7 @@ describe("tests related to usage message generation", function() it("creates correct usage message for commands", function() local parser = argparse.parser "foo" + :add_help(false) parser:flag "-q" "--quiet" local run = parser:command "run" run:option "--where" @@ -50,8 +54,10 @@ describe("tests related to usage message generation", function() it("creates correct usage message for subcommands", function() local parser = argparse.parser "foo" + :add_help(false) parser:flag "-q" "--quiet" local run = parser:command "run" + :add_help(false) run:option "--where" parser:prepare() @@ -66,6 +72,7 @@ describe("tests related to usage message generation", function() it("uses message provided by user", function() local parser = argparse.parser "foo" :usage "Usage: obvious" + :add_help(false) parser:flag "-q" "--quiet" assert.equal( @@ -76,6 +83,7 @@ describe("tests related to usage message generation", function() it("uses per-option message provided by user", function() local parser = argparse.parser "foo" + :add_help(false) parser:flag "-q" "--quiet" :usage "[-q | --quiet]" @@ -87,6 +95,7 @@ describe("tests related to usage message generation", function() it("uses argnames provided by user", function() local parser = argparse.parser "foo" + :add_help(false) parser:argument "inputs" :args "1-2" :argname "" diff --git a/src/argparse.lua b/src/argparse.lua index a7165af..87d31f1 100644 --- a/src/argparse.lua +++ b/src/argparse.lua @@ -51,9 +51,10 @@ local Parser = class { _options = {}, _commands = {}, _require_command = false, + _add_help = true, _fields = { "name", "description", "target", "require_command", - "action", "usage" + "action", "usage", "help", "add_help" } }:include(Declarative) @@ -169,7 +170,7 @@ function Parser:error(fmt, ...) if _TEST then error(msg) else - io.stderr:write(("%s\r\nError: %s\r\n"):format(self:get_usage(), msg)) + io.stderr:write(("%s\r\n\r\nError: %s\r\n"):format(self:get_usage(), msg)) os.exit(1) end end @@ -275,7 +276,7 @@ function Parser:make_types() if element._maxcount == 1 then if element._maxargs == 0 then element._type = "flag" - elseif element._maxargs == 1 and element._minargs == 1 then + elseif element._maxargs == 1 and (element._minargs == 1 or element._mincount == 1) then element._type = "arg" else element._type = "multi-arg" @@ -294,11 +295,21 @@ function Parser:make_types() end function Parser:prepare() + if self._add_help then + self:flag "-h" "--help" + :description "Show this help message and exit. " + :action(function() + print(self:get_help()) + os.exit(0) + end) + end + self:make_charset() self:make_targets() self:make_boundaries() self:make_command_names() self:make_types() + return self end @@ -329,6 +340,88 @@ function Parser:get_usage() return self._usage end +local function make_two_columns(s1, s2) + if #s1 < 22 then + return " " .. s1 .. (" "):rep(22 - #s1) .. s2 + else + if s2 == "" then + return " " .. s1 + else + return " " .. s1 .. "\r\n" .. (" "):rep(25) .. s2 + end + end +end + +local function make_description(element) + if element._default then + if element._description then + return ("%s (default: %s)"):format(element._description, element._default) + else + return ("default: %s"):format(element._default) + end + else + return element._description or "" + end +end + +local function make_name(option) + local variants = {} + local variant + + for _, alias in ipairs(option._aliases) do + variant = option:get_arg_usage("<" .. option._target .. ">") + table.insert(variant, 1, alias) + variant = table.concat(variant, " ") + table.insert(variants, variant) + end + + return table.concat(variants, ", ") +end + +function Parser:get_help() + if not self._help then + local blocks = {self:get_usage()} + + if self._description then + table.insert(blocks, self._description) + end + + if #self._arguments > 0 then + local buf = {"Arguments: "} + + for _, argument in ipairs(self._arguments) do + table.insert(buf, make_two_columns(argument._name, make_description(argument))) + end + + table.insert(blocks, table.concat(buf, "\r\n")) + end + + if #self._options > 0 then + local buf = {"Options: "} + + for _, option in ipairs(self._options) do + table.insert(buf, make_two_columns(make_name(option), make_description(option))) + end + + table.insert(blocks, table.concat(buf, "\r\n")) + end + + if #self._commands > 0 then + local buf = {"Commands: "} + + for _, command in ipairs(self._commands) do + table.insert(buf, make_two_columns(table.concat(command._aliases, ", "), command._description or "")) + end + + table.insert(blocks, table.concat(buf, "\r\n")) + end + + self._help = table.concat(blocks, "\r\n\r\n") + end + + return self._help +end + local function get_tip(context, wrong_name) local context_pool = {} local possible_name