module SimpleScripting::Argv
Public Instance Methods
decode(*definition_and_options)
click to toggle source
# File lib/simple_scripting/argv.rb, line 50 def decode(*definition_and_options) params_definition, options = decode_definition_and_options(definition_and_options) arguments = options.fetch(:arguments, ARGV) long_help = options[:long_help] auto_help = options.fetch(:auto_help, true) output = options.fetch(:output, $stdout) raise_errors = options.fetch(:raise_errors, false) # WATCH OUT! @long_help can also be set in :decode_command!. See issue #17. # @long_help = long_help exit_data = catch(:exit) do if params_definition.first.is_a?(Hash) return decode_command!(params_definition, arguments, auto_help) else return decode_arguments!(params_definition, arguments, auto_help) end end exit_data.print_help(output, @long_help) nil # to be used with the 'decode(...) || exit' pattern rescue SimpleScripting::Argv::ArgumentError, OptionParser::InvalidOption => error raise if raise_errors output.puts "Command error!: #{error.message}" rescue SimpleScripting::Argv::InvalidCommand => error raise if raise_errors output.puts <<~MESSAGE Command error!: #{error.message}" Valid commands: #{error.valid_commands.join(", ")} MESSAGE ensure @long_help = nil end
Private Instance Methods
check_no_remaining_arguments(arg_values)
click to toggle source
# File lib/simple_scripting/argv.rb, line 279 def check_no_remaining_arguments(arg_values) raise ArgumentError.new("Too many arguments") if !arg_values.empty? end
compose_returned_commands(commands_stack)
click to toggle source
HELPERS ##############################################
# File lib/simple_scripting/argv.rb, line 285 def compose_returned_commands(commands_stack) commands_stack.join('.') end
decode_arguments!(params_definition, arg_values, auto_help, commands_stack=[])
click to toggle source
# File lib/simple_scripting/argv.rb, line 176 def decode_arguments!(params_definition, arg_values, auto_help, commands_stack=[]) result = {} parser_opts_copy = nil # not available outside the block arg_definitions = {} # { 'name' => mandatory? } OptionParser.new do |parser_opts| params_definition.each do |param_definition| case param_definition when Array process_option_definition!(param_definition, parser_opts, result) when String process_argument_definition!(param_definition, arg_definitions) else # This is an error in the params definition, so it doesn't follow the user error/help # workflow. # raise "Unrecognized value: #{param_definition}" end end # See --help note in :decode_command!. # parser_opts.on('-h', '--help', 'Help') do if auto_help throw :exit, ExitWithArgumentsHelpPrinting.new(commands_stack, arg_definitions, parser_opts_copy) else # Needs to be better handled. When help is required, generally, it trumps the # correctness of the rest of the options/arguments. # result[:help] = true end end parser_opts_copy = parser_opts end.parse!(arg_values) arg_definitions.each do |arg_name, arg_is_mandatory| if arg_name.to_s.start_with?('*') arg_name = arg_name.to_s[1..-1].to_sym process_varargs!(arg_values, result, commands_stack, arg_name, arg_is_mandatory) else process_regular_argument!(arg_values, result, commands_stack, arg_name, arg_is_mandatory) end end check_no_remaining_arguments(arg_values) result end
decode_command!(params_definition, arguments, auto_help, commands_stack=[])
click to toggle source
Input params_definition for a non-nested case:
[{"command1"=>["arg1", {:long_help=>"This is the long help."}], "command2"=>["arg2"]}]
# File lib/simple_scripting/argv.rb, line 123 def decode_command!(params_definition, arguments, auto_help, commands_stack=[]) commands_definition = params_definition.first # Set the `command` variable only after; in the case where we print the help, this variable # must be unset. # command_for_check = arguments.shift # Note that `--help` is baked into OptParse, so without a workaround, we need to always include # it. # if command_for_check == '-h' || command_for_check == '--help' if auto_help throw :exit, ExitWithCommandsHelpPrinting.new(commands_definition) else # This is tricky. Since the common behavior of `--help` is to trigger an unconditional # help, it's not clear what to do with other tokens. For simplicity, we just return # this flag. # return { help: true } end end command = command_for_check raise InvalidCommand.new("Missing command!", commands_definition.keys) if command.nil? command_params_definition = commands_definition[command] case command_params_definition when nil raise InvalidCommand.new("Invalid command: #{command}", commands_definition.keys) when Hash commands_stack << command # Nested case! Decode recursively # decode_command!([command_params_definition], arguments, auto_help, commands_stack) else commands_stack << command if command_params_definition.last.is_a?(Hash) internal_params = command_params_definition.pop # only long_help is here, if present @long_help = internal_params.delete(:long_help) end [ compose_returned_commands(commands_stack), decode_arguments!(command_params_definition, arguments, auto_help, commands_stack), ] end end
decode_definition_and_options(definition_and_options)
click to toggle source
This is trivial to define with named arguments, however, Ruby 2.6 removed the support for mixing strings and symbols as argument keys, so we're forced to perform manual decoding. The complexity of this code supports the rationale for the removal of the functionality.
# File lib/simple_scripting/argv.rb, line 96 def decode_definition_and_options(definition_and_options) # Only a hash (commands) if definition_and_options.size == 1 && definition_and_options.first.is_a?(Hash) options = definition_and_options.first.each_with_object({}) do |(key, value), current_options| current_options[key] = definition_and_options.first.delete(key) if key.is_a?(Symbol) end # If there is an empty hash left, we remove it, so it's not considered commands. # definition_and_options = [] if definition_and_options.first.empty? # Options passed elsif definition_and_options.last.is_a?(Hash) options = definition_and_options.pop # No options passed else options = {} end [definition_and_options, options] end
process_argument_definition!(param_definition, args)
click to toggle source
# File lib/simple_scripting/argv.rb, line 250 def process_argument_definition!(param_definition, args) if param_definition.start_with?('[') arg_name = param_definition[1 .. -2].to_sym args[arg_name] = false else arg_name = param_definition.to_sym args[arg_name] = true end end
process_option_definition!(param_definition, parser_opts, result)
click to toggle source
DEFINITIONS PROCESSING ###############################
# File lib/simple_scripting/argv.rb, line 228 def process_option_definition!(param_definition, parser_opts, result) # Work on a copy; in at least one case (data type definition), we perform destructive # operations. # param_definition = param_definition.dup if param_definition[1] && param_definition[1].start_with?('--') raw_key, key_argument = param_definition[1].split(' ') key = raw_key[2 .. -1].tr('-', '_').to_sym if key_argument&.include?(',') param_definition.insert(2, Array) end else key = param_definition[0][1 .. -1].to_sym end parser_opts.on(*param_definition) do |value| result[key] = value || true end end
process_regular_argument!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
click to toggle source
# File lib/simple_scripting/argv.rb, line 269 def process_regular_argument!(arg_values, result, commands_stack, arg_name, arg_is_mandatory) if arg_values.empty? if arg_is_mandatory raise ArgumentError.new("Missing mandatory argument(s)") end else result[arg_name] = arg_values.shift end end
process_varargs!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
click to toggle source
# File lib/simple_scripting/argv.rb, line 262 def process_varargs!(arg_values, result, commands_stack, arg_name, arg_is_mandatory) raise ArgumentError.new("Missing mandatory argument(s)") if arg_is_mandatory && arg_values.empty? result[arg_name] = arg_values.dup arg_values.clear end