Class: Aspera::Cli::Parser
- Inherits:
-
Object
- Object
- Aspera::Cli::Parser
- Defined in:
- lib/aspera/cli/parser.rb
Overview
parse command line options arguments options start with '-', others are commands resolves on extended value syntax
Instance Attribute Summary collapse
-
#ask_missing_mandatory ⇒ Object
Returns the value of attribute ask_missing_mandatory.
-
#ask_missing_optional ⇒ Object
Returns the value of attribute ask_missing_optional.
-
#declared_options ⇒ Hash{Symbol => OptionValue}
readonly
All declared options (read-only view).
-
#fail_on_missing_mandatory ⇒ Object
writeonly
Sets the attribute fail_on_missing_mandatory.
-
#help_requested ⇒ Object
Returns the value of attribute help_requested.
Class Method Summary collapse
-
.get_from_list(short_value, descr, allowed_values) ⇒ Object
Find shortened string value in allowed symbol list.
-
.match_prefix(short_value, allowed_values) ⇒ Object?
Find a key in a list by exact match or unique prefix match.
-
.multi_choice_assert_msg(error_msg, accept_list) ⇒ Object
Generates error message with list of allowed values.
-
.option_line_to_name(name) ⇒ String
Change option name with dash to name with underscore.
- .option_name_to_line(name) ⇒ Object
-
.percent_selector(identifier) ⇒ Hash{Symbol => String}?
{field:,value:}if identifier is a percent selector, elsenil.
Instance Method Summary collapse
-
#add_option_preset(preset_hash, where, override: true) ⇒ Object
Adds each of the keys of specified hash as an option.
-
#add_types_info(types) ⇒ String
Add a type to the message if not special types.
-
#args_as_extended(end_marker) ⇒ Hash, Array
Read remaining args and build an
ArrayorHash. -
#clear_option(option_symbol) ⇒ Object
Set option to
nil. -
#command_or_arg_empty? ⇒ Boolean
Check if there were unprocessed values to generate error.
-
#declare(option_symbol, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil, &block) ⇒ Object
Declare an option.
-
#final_errors ⇒ Object
Unprocessed options or arguments ?.
-
#get_interactive(descr, check_option: false, multiple: false, accept_list: nil, schema: nil) ⇒ String
Prompt user for input in a list of symbols.
-
#get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Allowed::TYPES_STRING, aliases: nil, default: nil, schema: nil) ⇒ Object, ...
private
Low-level positional argument reader.
- #get_next_command(command_list, aliases: nil) ⇒ Object
-
#get_option(option_symbol, mandatory: false, schema: nil) ⇒ Object
Get an option value by name either return value or calls handler, can return nil ask interactively if requested/required.
-
#group(name) ⇒ Object
Set the current help section group name for subsequent declarations.
-
#help_text(banner: nil) ⇒ String
Generate help text for all declared options, grouped by section.
-
#initialize(program_name, argv = nil) ⇒ Parser
constructor
A new instance of Parser.
-
#instance_identifier(description: 'identifier', &block) {|field, value| ... } ⇒ String+
Resource identifier as positional parameter.
-
#known_options(only_defined: false) ⇒ Hash
Options as taken from config file and command line just before command execution.
-
#option_declared?(option_symbol) ⇒ Boolean
Check whether an option has already been declared in this manager.
-
#option_def(option_symbol) ⇒ OptionValue
Get an option definition by name.
-
#parse_options! ⇒ Object
Removes already known options from the list.
- #prompt_user_input(prompt, sensitive: false) ⇒ Object
-
#prompt_user_input_in_list(prompt, sym_list) ⇒ Symbol
prompt user for input in a list of symbols.
-
#rename_current_group(name) ⇒ Object
Rename all options currently tagged with @current_group to a new name, then update @current_group.
-
#set_handler(option_symbol, object:, method:) ⇒ nil
Bind (or re-bind) a runtime handler to an already-declared option.
-
#set_option(option_symbol, value, where: 'code override') ⇒ Object
Set an option value by name, either store value or call handler String is given to extended value.
-
#unprocessed_options_with_value ⇒ Hash
Get all original options on command line used to generate a config in config file.
-
#unshift_next_argument(argument) ⇒ Object
Allows a plugin to add an argument as next argument to process.
Constructor Details
#initialize(program_name, argv = nil) ⇒ Parser
Returns a new instance of Parser.
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/aspera/cli/parser.rb', line 328 def initialize(program_name, argv = nil) # Option descriptions: maps option symbol to its OptionValue descriptor # @type [Hash{Symbol => OptionValue}] @declared_options = {} # do we ask missing options and arguments to user ? @ask_missing_mandatory = false # STDIN.isatty # ask optional options if not provided and in interactive @ask_missing_optional = false # get_option fails if a mandatory parameter is asked @fail_on_missing_mandatory = true # set to true when --help / -h is parsed @help_requested = false # options can also be provided by env vars : --param-name -> ASCLI_PARAM_NAME @option_pairs_batch = {} @option_pairs_env = {} # Short option char -> option symbol, e.g. {'h' => :help, 'v' => :version} @short_options = {} # Current help section group name, set by #group @current_group = 'global' env_prefix = program_name.upcase + OPTION_SEP_SYMBOL ENV.each do |k, v| @option_pairs_env[k.delete_prefix(env_prefix).downcase.to_sym] = v if k.start_with?(env_prefix) end Log.log.debug{"env=#{@option_pairs_env}".red} # command line values starting with at least one '-' @unprocessed_cmd_line_options = [] # command line values *not* starting with '-' @unprocessed_cmd_line_arguments = [] # a copy of all initial options @initial_cli_options = [] # For each option string: list (one entry per occurrence) of the number of positional args # that appear before it in original argv. Used by `@:` in option values to skip preceding args. # @type [Hash{String => Array<Integer>}] @args_before_option = {} return if argv.nil? # true until `--` is found (stop options) = true arg_count = 0 argv.each do |value| if && value.start_with?('-') Log.log.trace1{"opt: #{value}"} if value.eql?(OPTIONS_STOP) = false else @unprocessed_cmd_line_options.push(value) (@args_before_option[value] ||= []).push(arg_count) end else Log.log.trace1{"arg: #{value}"} @unprocessed_cmd_line_arguments.push(value) arg_count += 1 end end # Total positional args at parse time - used in args_as_extended to compute how many to skip. @arg_total_count = @unprocessed_cmd_line_arguments.length # Number of original positional args before the option currently being parsed (nil = positional context). @current_option_args_offset = nil @initial_cli_options = @unprocessed_cmd_line_options.dup.freeze Log.log.trace1{"add_cmd_line_options:commands/arguments=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red} declare(:interactive, description: 'Use interactive input of missing params', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :ask_missing_mandatory}) declare(:ask_options, description: 'Ask even optional options', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :ask_missing_optional}) # do not parse options yet, let's wait for option `-h` to be overridden end |
Instance Attribute Details
#ask_missing_mandatory ⇒ Object
Returns the value of attribute ask_missing_mandatory.
323 324 325 |
# File 'lib/aspera/cli/parser.rb', line 323 def ask_missing_mandatory @ask_missing_mandatory end |
#ask_missing_optional ⇒ Object
Returns the value of attribute ask_missing_optional.
323 324 325 |
# File 'lib/aspera/cli/parser.rb', line 323 def ask_missing_optional @ask_missing_optional end |
#declared_options ⇒ Hash{Symbol => OptionValue} (readonly)
Returns all declared options (read-only view).
554 555 556 |
# File 'lib/aspera/cli/parser.rb', line 554 def @declared_options end |
#fail_on_missing_mandatory=(value) ⇒ Object (writeonly)
Sets the attribute fail_on_missing_mandatory
324 325 326 |
# File 'lib/aspera/cli/parser.rb', line 324 def fail_on_missing_mandatory=(value) @fail_on_missing_mandatory = value end |
#help_requested ⇒ Object
Returns the value of attribute help_requested.
323 324 325 |
# File 'lib/aspera/cli/parser.rb', line 323 def help_requested @help_requested end |
Class Method Details
.get_from_list(short_value, descr, allowed_values) ⇒ Object
Find shortened string value in allowed symbol list
274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/aspera/cli/parser.rb', line 274 def get_from_list(short_value, descr, allowed_values) Aspera.assert_type(short_value, String) # we accept shortcuts matching_exact = allowed_values.select{ |i| i.to_s.eql?(short_value)} return matching_exact.first if matching_exact.length == 1 matching = allowed_values.select{ |i| i.to_s.start_with?(short_value)} raise BadArgument, "Identifier '#{short_value}' used where a #{descr} is expected: place the identifier after the command" if matching.empty? && short_value.match?(REGEX_LOOKUP_ID_BY_FIELD) Aspera.assert(!matching.empty?, multi_choice_assert_msg("unknown value for #{descr}: #{short_value}", allowed_values), type: BadArgument) Aspera.assert(matching.length.eql?(1), multi_choice_assert_msg("ambiguous shortcut for #{descr}: #{short_value}", matching), type: BadArgument) return BoolValue.true?(matching.first) if allowed_values.eql?(BoolValue::ALL) matching.first end |
.match_prefix(short_value, allowed_values) ⇒ Object?
Find a key in a list by exact match or unique prefix match
289 290 291 292 293 |
# File 'lib/aspera/cli/parser.rb', line 289 def match_prefix(short_value, allowed_values) return short_value if allowed_values.include?(short_value) matches = allowed_values.select{ |k| k.to_s.start_with?(short_value.to_s)} matches.length == 1 ? matches.first : nil end |
.multi_choice_assert_msg(error_msg, accept_list) ⇒ Object
Generates error message with list of allowed values
298 299 300 |
# File 'lib/aspera/cli/parser.rb', line 298 def multi_choice_assert_msg(error_msg, accept_list) [error_msg, 'Use:', *accept_list.map{ |choice| "- #{choice}"}.sort].join("\n") end |
.option_line_to_name(name) ⇒ String
Change option name with dash to name with underscore
305 306 307 |
# File 'lib/aspera/cli/parser.rb', line 305 def option_line_to_name(name) name.gsub(OPTION_SEP_LINE, OPTION_SEP_SYMBOL) end |
.option_name_to_line(name) ⇒ Object
309 310 311 |
# File 'lib/aspera/cli/parser.rb', line 309 def option_name_to_line(name) "#{OPTION_PREFIX}#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}" end |
.percent_selector(identifier) ⇒ Hash{Symbol => String}?
Returns {field:,value:} if identifier is a percent selector, else nil.
314 315 316 317 318 319 320 |
# File 'lib/aspera/cli/parser.rb', line 314 def percent_selector(identifier) Aspera.assert_type(identifier, String) if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD)) return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")} end nil end |
Instance Method Details
#add_option_preset(preset_hash, where, override: true) ⇒ Object
Adds each of the keys of specified hash as an option
629 630 631 632 633 634 635 636 |
# File 'lib/aspera/cli/parser.rb', line 629 def add_option_preset(preset_hash, where, override: true) Aspera.assert_type(preset_hash, Hash) Log.log.debug{"add_option_preset: #{preset_hash}, #{where}, #{override}"} preset_hash.each do |k, v| option_symbol = k.to_sym @option_pairs_batch[option_symbol] = v if override || !@option_pairs_batch.key?(option_symbol) end end |
#add_types_info(types) ⇒ String
Add a type to the message if not special types
395 396 397 398 |
# File 'lib/aspera/cli/parser.rb', line 395 def add_types_info(types) return '' if !types || types.empty? || types.eql?(Allowed::TYPES_ENUM) || types.eql?(Allowed::TYPES_BOOLEAN) || types.eql?(Allowed::TYPES_STRING) " (#{types.map(&:name).join(', ')})" end |
#args_as_extended(end_marker) ⇒ Hash, Array
Read remaining args and build an Array or Hash
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 |
# File 'lib/aspera/cli/parser.rb', line 801 def args_as_extended(end_marker) # This extended value does not take args (`@:`) # ExtendedValue.assert_no_value(end_marker, :p) end_marker = SpecialValues::EOA if end_marker.empty? # When called from an option value, skip positional args that appear before the option in argv. # @current_option_args_offset holds the number of original args before the option (nil = positional context). # The number to actually skip = args_before_option - args_already_consumed (clamped to 0). skip_count = if @current_option_args_offset [@current_option_args_offset - (@arg_total_count - @unprocessed_cmd_line_arguments.length), 0].max else 0 end skipped = skip_count.positive? ? @unprocessed_cmd_line_arguments.shift(skip_count) : [] Log.log.trace1{"args_as_extended: skipping #{skipped.length} args before option: #{skipped}"} unless skipped.empty? result = nil get_next_argument('args', multiple: end_marker).each do |argument| Aspera.assert(argument.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{argument} does not include #{OPTION_VALUE_SEPARATOR}"} path, value = argument.split(OPTION_VALUE_SEPARATOR, 2) result = DotContainer.dotted_to_container(path.split(DotContainer::SEPARATOR), smart_convert(value), result) end # Restore skipped args so they remain available for command dispatching @unprocessed_cmd_line_arguments.unshift(*skipped) unless skipped.empty? result end |
#clear_option(option_symbol) ⇒ Object
Set option to nil
608 609 610 611 |
# File 'lib/aspera/cli/parser.rb', line 608 def clear_option(option_symbol) Aspera.assert_type(option_symbol, Symbol) option_def(option_symbol).clear end |
#command_or_arg_empty? ⇒ Boolean
Check if there were unprocessed values to generate error
644 645 646 |
# File 'lib/aspera/cli/parser.rb', line 644 def command_or_arg_empty? @unprocessed_cmd_line_arguments.empty? end |
#declare(option_symbol, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil, &block) ⇒ Object
Declare an option
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/aspera/cli/parser.rb', line 410 def declare(option_symbol, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil, &block) Aspera.assert_type(option_symbol, Symbol) Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"} Aspera.assert_type(handler, Hash) if handler Aspera.assert(handler.keys.sort.eql?(%i[m o]), 'handler must have keys :m and :o') if handler option_attrs = @declared_options[option_symbol] = OptionValue.new( option: option_symbol, description: description, allowed: allowed, handler: handler, deprecation: deprecation, schema: schema ) option_attrs.group = @current_group description = option_attrs.description Aspera.assert(!description.nil?){"#{option_symbol}: no description and no schema to derive one from"} Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"} Aspera.assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with an uppercase"} Aspera.assert(!['hash', 'extended value'].any?{ |s| description.downcase.include?(s)}){"#{option_symbol} shall use :allowed instead of hash/extended value in option description"} set_option(option_symbol, default, where: 'default') unless default.nil? case option_attrs.types when Allowed::TYPES_ENUM, Allowed::TYPES_BOOLEAN # This option value must be a symbol (or array of symbols) set_option(option_symbol, BoolValue.true?(default), where: 'default') if option_attrs.values.eql?(BoolValue::ALL) && !default.nil? when Allowed::TYPES_NONE Aspera.assert_type(block, Proc){"missing execution block for #{option_symbol}"} option_attrs.block = block end @short_options[short] = option_symbol unless short.nil? Log.log.trace1{"declare: #{option_symbol}, group: #{@current_group}, short: #{short}"} end |
#final_errors ⇒ Object
Unprocessed options or arguments ?
649 650 651 652 653 654 |
# File 'lib/aspera/cli/parser.rb', line 649 def final_errors result = [] result.push("unprocessed options: #{@unprocessed_cmd_line_options}") unless @unprocessed_cmd_line_options.empty? result.push("unprocessed values: #{@unprocessed_cmd_line_arguments}") unless @unprocessed_cmd_line_arguments.empty? result end |
#get_interactive(descr, check_option: false, multiple: false, accept_list: nil, schema: nil) ⇒ String
Prompt user for input in a list of symbols
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 |
# File 'lib/aspera/cli/parser.rb', line 772 def get_interactive(descr, check_option: false, multiple: false, accept_list: nil, schema: nil) option_attrs = @declared_options[descr.to_sym] what = option_attrs ? 'option' : 'argument' default_prompt = "#{what}: #{descr}" if !@ask_missing_mandatory = "Missing #{default_prompt}" = self.class.multi_choice_assert_msg(, accept_list) if accept_list += "\n#{TerminalFormatter::HINT}Give `#{HELP}` as argument to retrieve the schema of the missing argument." if schema raise Cli::MissingArgument, end # ask interactively result = [] puts(' (one per line, end with empty line)') if multiple loop do prompt = default_prompt prompt = "#{accept_list.join(' ')}\n#{default_prompt}" if accept_list entry = prompt_user_input(prompt, sensitive: option_attrs&.sensitive) break if entry.empty? && multiple entry = ExtendedValue.instance.evaluate(entry, context: 'interactive input') entry = self.class.get_from_list(entry, descr, accept_list) if accept_list return entry unless multiple result.push(entry) end result end |
#get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Allowed::TYPES_STRING, aliases: nil, default: nil, schema: nil) ⇒ Object, ...
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Low-level positional argument reader. Prefer Base#resolve_argument from action methods.
Direct calls from outside Parser are legacy exceptions documented in ST12/ST13
(mixins without DSL: sync_actions, ascp_actions; setup callbacks: aoc.rb).
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/aspera/cli/parser.rb', line 469 def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Allowed::TYPES_STRING, aliases: nil, default: nil, schema: nil) Aspera.assert_array_all(accept_list, Symbol) unless accept_list.nil? Aspera.assert_hash_all(aliases, Symbol, Symbol) unless aliases.nil? validation = Symbol unless accept_list.nil? validation = [validation] unless validation.is_a?(Array) || validation.nil? Aspera.assert_array_all(validation, Class){'validation'} unless validation.nil? descr = "#{descr}#{add_types_info(validation)}" result = if !@unprocessed_cmd_line_arguments.empty? case multiple when true values = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length) when false values = [@unprocessed_cmd_line_arguments.shift] when String index = @unprocessed_cmd_line_arguments.index(multiple) if index values = @unprocessed_cmd_line_arguments.shift(index) @unprocessed_cmd_line_arguments.shift # remove end marker else values = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length) end else Aspera.error_unexpected_value(multiple){'multiple'} end values = values.map{ |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation)} # If expecting list and only one arg of type array : it is the list values = values.first if multiple && values.length.eql?(1) && values.first.is_a?(Array) if accept_list allowed_values = [].concat(accept_list) allowed_values.concat(aliases.keys) unless aliases.nil? values = values.map{ |v| self.class.get_from_list(v, descr, allowed_values)} end multiple ? values : values.first elsif !default.nil? then default # no value provided, either get value interactively, or exception elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list, schema: schema) end if result.is_a?(String) && validation&.eql?(Allowed::TYPES_INTEGER) int_result = Integer(result, exception: false) raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil? result = int_result end Log.log.trace1{"#{descr}=#{result}"} result = aliases[result] if aliases&.key?(result) # if value comes from JSON/YAML, it may come as Integer result = result.to_s if result.is_a?(Integer) && validation&.eql?(Allowed::TYPES_STRING) if validation && (mandatory || !result.nil?) value_list = multiple ? result : [result] value_list.each do |value| raise SchemaRequest.new(:argument, descr, schema) if validation.include?(Hash) && value.eql?(HELP) raise Cli::BadArgument, "Argument #{descr} is a #{value.class} but must be #{'one of: ' if validation.length > 1}#{validation.map(&:name).join(', ')}" unless validation.any?{ |t| value.is_a?(t)} end end result end |
#get_next_command(command_list, aliases: nil) ⇒ Object
544 |
# File 'lib/aspera/cli/parser.rb', line 544 def get_next_command(command_list, aliases: nil); get_next_argument('command', accept_list: command_list, aliases: aliases); end |
#get_option(option_symbol, mandatory: false, schema: nil) ⇒ Object
Get an option value by name either return value or calls handler, can return nil ask interactively if requested/required
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/aspera/cli/parser.rb', line 572 def get_option(option_symbol, mandatory: false, schema: nil) Aspera.assert_type(option_symbol, Symbol) option_attrs = option_def(option_symbol) result = option_attrs.value # Contextual schema: raise SchemaRequest when value is 'help' raise SchemaRequest.new(:option, option_symbol.to_s, schema) if schema && result.eql?(HELP) # Do not fail for manual generation if option mandatory but not set return :skip_missing_mandatory if result.nil? && mandatory && !@fail_on_missing_mandatory if result.nil? if !@ask_missing_mandatory Aspera.assert(!mandatory, type: Cli::BadArgument){"Missing mandatory option: #{option_symbol}"} elsif @ask_missing_optional || mandatory # ask_missing_mandatory result = get_interactive(option_symbol.to_s, check_option: true, accept_list: option_attrs.values, schema: option_attrs.schema) set_option(option_symbol, result, where: 'interactive') end end result end |
#group(name) ⇒ Object
Set the current help section group name for subsequent declarations
444 445 446 |
# File 'lib/aspera/cli/parser.rb', line 444 def group(name) @current_group = name end |
#help_text(banner: nil) ⇒ String
Generate help text for all declared options, grouped by section.
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 |
# File 'lib/aspera/cli/parser.rb', line 829 def help_text(banner: nil) rows = [] current_group = nil @declared_options.each do |sym, opt| if opt.group != current_group current_group = opt.group rows << [{value: "OPTIONS: #{current_group}", colspan: 2}] end short_char = @short_options.key(sym) short_part = short_char ? "-#{short_char}, " : ' ' flag = "#{short_part}#{symbol_to_option(sym, option_display_value(opt))}" rows << [flag, opt.description] end table = Terminal::Table.new(rows: rows, style: {border: HELP_BORDER, padding_left: 0, padding_right: 2}) .nil? ? table.to_s : "#{}\n#{table}" end |
#instance_identifier(description: 'identifier', &block) {|field, value| ... } ⇒ String+
Resource identifier as positional parameter
534 535 536 537 538 539 540 541 542 |
# File 'lib/aspera/cli/parser.rb', line 534 def instance_identifier(description: 'identifier', &block) res_id = get_next_argument(description, multiple: get_option(:bulk)) # Can be an Array if res_id.is_a?(String) && (m = Parser.percent_selector(res_id)) Aspera.assert(block_given?, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"} res_id = yield(m[:field], m[:value]) end res_id end |
#known_options(only_defined: false) ⇒ Hash
Returns options as taken from config file and command line just before command execution.
677 678 679 680 681 682 683 684 685 686 |
# File 'lib/aspera/cli/parser.rb', line 677 def (only_defined: false) result = {} @declared_options.each_key do |option_symbol| v = get_option(option_symbol) result[option_symbol] = v unless only_defined && v.nil? rescue => e result[option_symbol] = e.to_s end result end |
#option_declared?(option_symbol) ⇒ Boolean
Check whether an option has already been declared in this manager
549 550 551 |
# File 'lib/aspera/cli/parser.rb', line 549 def option_declared?(option_symbol) @declared_options.key?(option_symbol) end |
#option_def(option_symbol) ⇒ OptionValue
Get an option definition by name
560 561 562 563 |
# File 'lib/aspera/cli/parser.rb', line 560 def option_def(option_symbol) Aspera.assert(@declared_options.key?(option_symbol), type: Cli::BadArgument){"Unknown option: #{option_symbol}"} @declared_options[option_symbol] end |
#parse_options! ⇒ Object
Removes already known options from the list
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 |
# File 'lib/aspera/cli/parser.rb', line 689 def Log.log.trace1('parse_options!'.red) # First options from conf file @option_pairs_batch = consume_option_pairs(@option_pairs_batch, 'set') # Then, env var (to override) @option_pairs_env = consume_option_pairs(@option_pairs_env, 'env') # Then, command line override: process one option at a time so that @current_option_args_offset # can be set before each option is evaluated (used by `@:` extended value in option values). = [] Log.log.trace1('Before parse') Log.dump(:unprocessed_cmd_line_options, @unprocessed_cmd_line_options, level: :trace1) until @unprocessed_cmd_line_options.empty? opt = @unprocessed_cmd_line_options.shift # Expose args_before for this option so `args_as_extended` can skip args preceding it. # Peek (first) without consuming - consumed only if this option is processed (not deferred). @current_option_args_offset = @args_before_option[opt]&.first if opt.start_with?(OPTION_PREFIX) # Long option: --name or --name=value name_raw, raw_value = opt.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2) option_sym = self.class.option_line_to_name(name_raw).to_sym resolved_sym = self.class.match_prefix(option_sym, @declared_options.keys) if resolved_sym dispatch_option(resolved_sym, raw_value) @args_before_option[opt]&.shift # consumed: advance to next occurrence else # Dotted notation: --a.b.c=d does: a={"b":{"c":ext_val(d)}} Log.log.trace1{"Unknown long option: #{opt}".red} if !raw_value.nil? path = name_raw.split(DotContainer::SEPARATOR) root_sym = self.class.option_line_to_name(path.shift).to_sym if @declared_options.key?(root_sym) set_option(root_sym, DotContainer.dotted_to_container(path, smart_convert(raw_value), get_option(root_sym)), where: 'dotted') @args_before_option[opt]&.shift # consumed: advance to next occurrence next end end # Unknown option: defer to next parse_options! round, do not consume the recorded offset .push(opt) end elsif opt.start_with?('-') && (option_sym = @short_options[opt[1]]) # Short option: -h, -v, or -Pvalue (value glued to flag) dispatch_option(option_sym, opt.length > 2 ? opt[2..] : nil) @args_before_option[opt]&.shift # consumed: advance to next occurrence else .push(opt) end end @current_option_args_offset = nil Log.log.trace1('After parse') Log.log.trace1{"remains: #{}"} # Set unprocessed options for next time @unprocessed_cmd_line_options = end |
#prompt_user_input(prompt, sensitive: false) ⇒ Object
743 744 745 746 747 748 749 |
# File 'lib/aspera/cli/parser.rb', line 743 def prompt_user_input(prompt, sensitive: false) return $stdin.getpass("#{prompt}> ") if sensitive print("#{prompt}> ") line = $stdin.gets Aspera.assert_type(line, String){'Unexpected end of standard input'} line.chomp end |
#prompt_user_input_in_list(prompt, sym_list) ⇒ Symbol
prompt user for input in a list of symbols
755 756 757 758 759 760 761 762 763 764 |
# File 'lib/aspera/cli/parser.rb', line 755 def prompt_user_input_in_list(prompt, sym_list) loop do input = prompt_user_input(prompt).to_sym if sym_list.any?{ |a| a.eql?(input)} return input else $stderr.puts("No such #{prompt}: #{input}, select one of: #{sym_list.join(', ')}") # rubocop:disable Style/StderrPuts end end end |
#rename_current_group(name) ⇒ Object
Rename all options currently tagged with @current_group to a new name, then update @current_group. Used by add_manual_header when a plugin declares its options before its group name is known (e.g. Plugins::Config).
452 453 454 455 |
# File 'lib/aspera/cli/parser.rb', line 452 def rename_current_group(name) @declared_options.each_value{ |opt| opt.group = name if opt.group.eql?(@current_group)} @current_group = name end |
#set_handler(option_symbol, object:, method:) ⇒ nil
Bind (or re-bind) a runtime handler to an already-declared option. Called from plugin initialize() for Category C handlers whose target object (e.g. @gen_options) is created after class-load time.
620 621 622 623 |
# File 'lib/aspera/cli/parser.rb', line 620 def set_handler(option_symbol, object:, method:) Aspera.assert_type(option_symbol, Symbol) option_def(option_symbol).bind_handler(o: object, m: method) end |
#set_option(option_symbol, value, where: 'code override') ⇒ Object
Set an option value by name, either store value or call handler String is given to extended value
597 598 599 600 601 602 603 604 605 |
# File 'lib/aspera/cli/parser.rb', line 597 def set_option(option_symbol, value, where: 'code override') Aspera.assert_type(option_symbol, Symbol) option = option_def(option_symbol) # Raise immediately only when the option has a static schema: the schema is known at parse time. # When schema is nil (e.g. --query), 'help' is stored as-is and SchemaRequest is raised later # in get_option() with the contextual schema provided by the calling command. raise SchemaRequest.new(:option, option.option, option.schema) if option.types&.include?(Hash) && value.eql?(HELP) && option.schema option.assign_value(value, where: where) end |
#unprocessed_options_with_value ⇒ Hash
Get all original options on command line used to generate a config in config file
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/aspera/cli/parser.rb', line 658 def result = {} @initial_cli_options.each do |option_argument| # ignore short options next unless option_argument.start_with?(OPTION_PREFIX) name, value = option_argument.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2) # ignore options without value next if value.nil? Log.log.debug{"option #{name}=#{value}"} path = name.split(DotContainer::SEPARATOR) path[0] = self.class.option_line_to_name(path[0]) DotContainer.dotted_to_container(path, smart_convert(value), result) @unprocessed_cmd_line_options.delete(option_argument) end result end |
#unshift_next_argument(argument) ⇒ Object
Allows a plugin to add an argument as next argument to process
639 640 641 |
# File 'lib/aspera/cli/parser.rb', line 639 def unshift_next_argument(argument) @unprocessed_cmd_line_arguments.unshift(argument) end |