lib/cmdparse.rb in cmdparse-3.0.1 vs lib/cmdparse.rb in cmdparse-3.0.2

- old
+ new

@@ -10,17 +10,46 @@ require 'optparse' OptionParser::Officious.delete('version') OptionParser::Officious.delete('help') + +# Extension for OptionParser objects to allow access to some internals. +class OptionParser #:nodoc: + + # Access the option list stack. + attr_reader :stack + + # Returns +true+ if at least one local option is defined. + # + # The zeroth stack element is not respected when doing the query because it contains either the + # OptionParser::DefaultList or a CmdParse::MultiList with the global options of the + # CmdParse::CommandParser. + def options_defined? + stack[1..-1].each do |list| + list.each_option do |switch| + return true if switch.kind_of?(OptionParser::Switch) && (switch.short || switch.long) + end + end + false + end + + # Returns +true+ if a banner has been set. + def banner? + !@banner.nil? + end + +end + + # Namespace module for cmdparse. # # See CmdParse::CommandParser and CmdParse::Command for the two important classes. module CmdParse # The version of this cmdparse implemention - VERSION = '3.0.1' + VERSION = '3.0.2'.freeze # Base class for all cmdparse errors. class ParseError < StandardError @@ -74,20 +103,25 @@ # This error is thrown when not enough arguments are provided for the command. class NotEnoughArgumentsError < ParseError reason 'Not enough arguments provided, minimum is' end + # This error is thrown when too many arguments are provided for the command. + class TooManyArgumentsError < ParseError + reason 'Too many arguments provided, maximum is' + end + # Command Hash - will return partial key matches as well if there is a single non-ambigous # matching key class CommandHash < Hash #:nodoc: def key?(name) #:nodoc: !self[name].nil? end def [](cmd_name) #:nodoc: - super or begin + super || begin possible = keys.select {|key| key[0, cmd_name.length] == cmd_name } fetch(possible[0]) if possible.size == 1 end end @@ -123,37 +157,10 @@ EOF end end - # Extension for OptionParser objects to allow access to some internals. - class ::OptionParser #:nodoc: - - # Access the option list stack. - attr_reader :stack - - # Returns +true+ if at least one local option is defined. - # - # The zeroth stack element is not respected when doing the query because it contains either the - # OptionParser::DefaultList or a CmdParse::MultiList with the global options of the - # CmdParse::CommandParser. - def options_defined? - stack[1..-1].each do |list| - list.each_option do |switch| - return true if ::OptionParser::Switch === switch && (switch.short || switch.long) - end - end - false - end - - # Returns +true+ if a banner has been set. - def banner? - !@banner.nil? - end - - end - # === Base class for commands # # This class implements all needed methods so that it can be used by the CommandParser class. # # Commands can either be created by sub-classing or on the fly when using the #add_command method. @@ -256,12 +263,12 @@ # Sets whether this command can take sub-command. # # The argument +val+ needs to be +true+ or +false+. def takes_commands(val) - if !val && commands.size > 0 - raise Error, "Can't change value of takes_commands to false because there are already sub-commands" + if !val && !commands.empty? + raise Error, "Can't change takes_commands to false because there are already sub-commands" else @takes_commands = val end end alias takes_commands= takes_commands @@ -484,11 +491,11 @@ # # A typical return value would look like the following: # # {command | other_command | another_command } def usage_commands - (commands.size > 0 ? "{#{commands.keys.sort.join(" | ")}}" : '') + (commands.empty? ? '' : "{#{commands.keys.sort.join(" | ")}}") end # Returns the formatted short description. # # For the output format see #cond_format_help_section @@ -509,11 +516,11 @@ # # For the output format see #cond_format_help_section def help_commands describe_commands = lambda do |command, level = 0| command.commands.sort.collect do |name, cmd| - str = " "*level << name << (name == command.default_command ? " (*)" : '') + str = " " * level << name << (name == command.default_command ? " (*)" : '') str = str.ljust(command_parser.help_desc_indent) << cmd.short_desc.to_s str = format(str, width: command_parser.help_line_width - command_parser.help_indent, indent: command_parser.help_desc_indent) str << "\n" << (cmd.takes_commands? ? describe_commands.call(cmd, level + 1) : "") end.join('') @@ -525,11 +532,11 @@ # Returns the formatted arguments of this command. # # For the output format see #cond_format_help_section def help_arguments desc = @argument_desc.map {|k, v| k.to_s.ljust(command_parser.help_desc_indent) << v.to_s} - cond_format_help_section('Arguments', desc, condition: @argument_desc.size > 0) + cond_format_help_section('Arguments', desc, condition: !@argument_desc.empty?) end # Returns the formatted option descriptions for the given OptionParser instance. # # The section title needs to be specified with the +title+ argument. @@ -552,11 +559,11 @@ def on_after_add end # For sorting commands by name. def <=>(other) - self.name <=> other.name + name <=> other.name end protected # Conditionally formats a help section. @@ -583,11 +590,11 @@ def cond_format_help_section(title, *lines, condition: true, indent: true, preformatted: false) if condition out = "#{title}:\n" lines = lines.flatten.join("\n").split(/\n/) if preformatted - lines.map! {|l| ' '*command_parser.help_indent << l} if indent + lines.map! {|l| ' ' * command_parser.help_indent << l} if indent out << lines.join("\n") else out << format(lines.join("\n"), indent: (indent ? command_parser.help_indent : 0), indent_first_line: true) end out << "\n\n" @@ -615,15 +622,15 @@ (first_line_pattern = /\A.{1,#{width}}\z|\A.{1,#{width}}[ \n]/m) unless indent_first_line pattern = first_line_pattern content.split(/\n\n/).map do |paragraph| lines = [] - while paragraph.length > 0 + unless paragraph.empty? unless (str = paragraph.slice!(pattern).sub(/[ \n]\z/, '')) str = paragraph.slice!(0, line_length) end - lines << (lines.empty? && !indent_first_line ? '' : ' '*indent) + str.gsub(/\n/, ' ') + lines << (lines.empty? && !indent_first_line ? '' : ' ' * indent) + str.tr("\n", ' ') pattern = other_lines_pattern end lines.join("\n") end.join("\n\n") end @@ -646,12 +653,12 @@ class HelpCommand < Command def initialize #:nodoc: super('help', takes_commands: false) short_desc('Provide help for individual commands') - long_desc('This command prints the program help if no arguments are given. If one or ' << - 'more command names are given as arguments, these arguments are interpreted ' << + long_desc('This command prints the program help if no arguments are given. If one or ' \ + 'more command names are given as arguments, these arguments are interpreted ' \ 'as a hierachy of commands and the help for the right most command is show.') argument_desc(COMMAND: 'The name of a command or sub-command') end def on_after_add #:nodoc: @@ -664,11 +671,11 @@ def usage_arguments #:nodoc: "[COMMAND COMMAND...]" end def execute(*args) #:nodoc: - if args.length > 0 + if !args.empty? cmd = command_parser.main_command arg = args.shift while !arg.nil? && cmd.commands.key?(arg) cmd = cmd.commands[arg] arg = args.shift @@ -787,12 +794,12 @@ # # Options: # # handle_exceptions:: Set to +true+ if exceptions should be handled gracefully by showing the # error and a help message, or to +false+ if exception should not be handled - # at all. If this options is set to :no_help, the exception is handled but no - # help message is shown. + # at all. If this options is set to :no_help, the exception is handled but + # no help message is shown. # # takes_commands:: Specifies whether the main program takes any commands. def initialize(handle_exceptions: false, takes_commands: true) @global_options = OptionParser.new @main_command = Command.new('main', takes_commands: takes_commands) @@ -866,10 +873,14 @@ @current_command = @current_command.commands[cmd_name] level += 1 else original_n = @current_command.arity n = (original_n < 0 ? -original_n - 1 : original_n) - raise NotEnoughArgumentsError.new(n) if argv.size < n + if argv.size < n + raise NotEnoughArgumentsError.new("#{n} - #{@current_command.usage_arguments}") + elsif argv.size > n && original_n > 0 + raise TooManyArgumentsError.new("#{n} - #{@current_command.usage_arguments}") + end argv.slice!(n..-1) unless original_n < 0 @current_command.execute(*argv) break end