Class: ArgParser::Definition

Inherits:
ArgumentScope show all
Defined in:
lib/arg-parser/definition.rb

Overview

Represents the collection of possible command-line arguments for a script.

Instance Attribute Summary collapse

Attributes inherited from ArgumentScope

#name, #parent, #predefined_args

Instance Method Summary collapse

Methods inherited from ArgumentScope

#<<, #[], #add_child, #args, #command_arg, #command_args, #command_args?, #flag_arg, #flag_args, #flag_args?, #has_key?, #key_used?, #keys, #keyword_arg, #keyword_args, #keyword_args?, #non_positional_args, #non_positional_args?, #positional_arg, #positional_args, #positional_args?, #rest_arg, #rest_args, #rest_args?, #short_keys, #size, #value_args, #walk_ancestors, #walk_arguments, #walk_children

Constructor Details

#initialize(name = 'ArgParser::Definition') {|_self| ... } ⇒ Definition

Create a new Definition, which is a collection of valid Arguments to be used when parsing a command-line.

Yields:

  • (_self)

Yield Parameters:



358
359
360
361
362
363
# File 'lib/arg-parser/definition.rb', line 358

def initialize(name = 'ArgParser::Definition')
    super(name)
    @require_set = []
    @title = $0.respond_to?(:titleize) ? $0.titleize : $0
    yield self if block_given?
end

Instance Attribute Details

Returns A copyright notice, displayed in the usage and help outputs.

Returns:

  • (String)

    A copyright notice, displayed in the usage and help outputs.



353
354
355
# File 'lib/arg-parser/definition.rb', line 353

def copyright
  @copyright
end

#purposeString

Returns A short description of the purpose of the script, for display when showing the usage help.

Returns:

  • (String)

    A short description of the purpose of the script, for display when showing the usage help.



350
351
352
# File 'lib/arg-parser/definition.rb', line 350

def purpose
  @purpose
end

#titleString

Returns A title for the script, displayed at the top of the usage and help outputs.

Returns:

  • (String)

    A title for the script, displayed at the top of the usage and help outputs.



347
348
349
# File 'lib/arg-parser/definition.rb', line 347

def title
  @title
end

Instance Method Details

#collapse(cmd_inst) ⇒ Definition

Collapses an ArgumentScope into this Definition, representing the collapsed argument possibilities once a command has been identitfied for a CommandArgument. Think of the original Definition as being a superposition of possible argument definitions, with one possible state for each CommandInstance of each commad. Once the actual CommandInstance is known, we are collapsing the superposition of possible definitions to a lower dimensionality; only one possible definition remains once all CommandArgument objects are replaced by CommandInstances.

Parameters:

  • cmd_inst (CommandInstance)

    The instance of a command that has been specified.

Returns:

  • (Definition)

    A new Definition with a set of arguments combined from this Definition and the selected ArgumentScope for a specific command instance.



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/arg-parser/definition.rb', line 381

def collapse(cmd_inst)
    new_def = self.clone
    child = cmd_inst.argument_scope
    new_args = {}
    new_short_keys = {}
    @arguments.each do |key, arg|
        if arg == cmd_inst.command_arg
            new_args[key] = cmd_inst
            child.walk_arguments do |key, arg|
                new_args[key] = arg
                new_short_keys[arg.short_key] = arg if arg.short_key
            end
        else
            new_args[key] = arg
            new_short_keys[arg.short_key] = arg if arg.short_key
        end
    end
    new_children = @children.reject{ |c| c == cmd_inst.argument_scope } &
        child.instance_variable_get(:@children)
    new_def.instance_variable_set(:@arguments, new_args)
    new_def.instance_variable_set(:@short_keys, new_short_keys)
    new_def.instance_variable_set(:@children, new_children)
    new_def
end

#errorsObject

Return an array of parse errors.

See Also:



484
485
486
# File 'lib/arg-parser/definition.rb', line 484

def errors
    parser.errors
end

#parse(args = ARGV) ⇒ OpenStruct, false

Parse the args array of arguments using this command-line definition.

arguments defined as accessors, and the parsed or default values for each argument as values. If unsuccessful, returns false indicating a parse failure.

Parameters:

  • args (Array, String) (defaults to: ARGV)

    an array of arguments, or a String representing the command-line that is to be parsed.

Returns:

  • (OpenStruct, false)

    if successful, an OpenStruct object with all

See Also:

  • Parser#errors, Parser#show_usage, Parser#show_help


477
478
479
# File 'lib/arg-parser/definition.rb', line 477

def parse(args = ARGV)
    parser.parse(args)
end

#parserParser

Returns a Parser instance that can be used to parse this command-line Definition.

Returns:

  • (Parser)

    a Parser instance that can be used to parse this command-line Definition.



463
464
465
# File 'lib/arg-parser/definition.rb', line 463

def parser
    @parser ||= Parser.new(self)
end

#predefined_arg(lookup_key, opts = {}) ⇒ Object

Lookup a pre-defined argument (created earlier via Argument#register), and add it to this arguments definition.

Parameters:

  • lookup_key (String, Symbol)

    The key under which the pre-defined argument was registered.

  • desc (String)

    An optional override for the argument description for this use of the pre-defined argument.

  • opts (Hash) (defaults to: {})

    An options hash for those select properties that can be overridden on a pre-defined argument.

Options Hash (opts):

  • :description (String)

    The argument description for this use of the pre-defined argument.

  • :usage_break (String)

    The usage break for this use of the pre-defined argument.

  • :required (Boolean)

    Whether this argument is a required (i.e. mandatory) argument.

  • :default (String)

    The default value for the argument, returned in the command-line parse results if no other value is specified.

See Also:



427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/arg-parser/definition.rb', line 427

def predefined_arg(lookup_key, opts = {})
    # TODO: walk ancestor chain looking at pre-defined arg scopes
    arg = (self.predefined_args && self.predefined_args.key_used?(lookup_key)) ||
        Argument.lookup(lookup_key)
    arg.short_key = opts[:short_key] if opts.has_key?(:short_key)
    arg.description = opts[:description] if opts.has_key?(:description)
    arg.usage_break = opts[:usage_break] if opts.has_key?(:usage_break)
    arg.required = opts[:required] if opts.has_key?(:required)
    arg.default = opts[:default] if opts.has_key?(:default)
    arg.on_parse = opts[:on_parse] if opts.has_key?(:on_parse)
    self << arg
end

#require_any_of(*keys) ⇒ Object

Individual arguments are optional, but at least one of keys arguments is required.



450
451
452
# File 'lib/arg-parser/definition.rb', line 450

def require_any_of(*keys)
    @require_set << [:any, keys.map{ |k| self[k] }]
end

#require_one_of(*keys) ⇒ Object

Individual arguments are optional, but exactly one of keys arguments is required.



443
444
445
# File 'lib/arg-parser/definition.rb', line 443

def require_one_of(*keys)
    @require_set << [:one, keys.map{ |k| self[k] }]
end

#requires_some?Boolean

True if at least one argument is required out of multiple optional args.

Returns:

  • (Boolean)


456
457
458
# File 'lib/arg-parser/definition.rb', line 456

def requires_some?
    @require_set.size > 0
end

#show_help(out = STDOUT, width = 80) ⇒ Array

Generates a more detailed help screen.

Parameters:

  • out (IO) (defaults to: STDOUT)

    an IO object on which the help information will be output. Pass nil if no output to any device is desired.

  • width (Integer) (defaults to: 80)

    the width at which to wrap text.

Returns:

  • (Array)

    An array of lines of text, containing the help text.



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/arg-parser/definition.rb', line 554

def show_help(out = STDOUT, width = 80)
    lines = ['', '']
    lines << title
    lines << title.gsub(/./, '=')
    lines << ''
    if purpose
        lines.concat(wrap_text(purpose, width))
        lines << ''
    end
    if copyright
        lines.concat(wrap_text("Copyright (c) #{copyright}", width))
        lines << ''
    end

    lines << 'USAGE'
    lines << '-----'
    pos_args = positional_args
    opt_args = size - pos_args.size
    usage_args = []
    usage_args.concat(pos_args.map(&:to_use))
    usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
    usage_args << rest_args.to_use if rest_args?
    lines.concat(wrap_text("  #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
    lines << ''

    if positional_args?
        max = positional_args.map{ |arg| arg.to_s.length }.max
        pos_args = positional_args
        pos_args << rest_args if rest_args?
        pos_args.each do |arg|
            if arg.usage_break
                lines << ''
                lines << arg.usage_break
            end
            desc = arg.description
            desc += "\n[Default: #{arg.default}]" unless arg.default.nil?
            wrap_text(desc, width - max - 6).each_with_index do |line, i|
                lines << "  %-#{max}s    %s" % [[arg.to_s][i], line]
            end
        end
        lines << ''
    end
    if command_args?
        max = command_args.reduce(0) do |max, cmd_arg|
            m = cmd_arg.commands.map{ |_, arg| arg.to_s.length }.max
            m > max ? m : max
        end
        command_args.each do |cmd_arg|
            lines << ''
            lines << "#{cmd_arg.to_use}S"
            lines << '--------'
            cmd_arg.commands.each do |_, arg|
                if arg.usage_break
                    lines << ''
                    lines << arg.usage_break
                end
                desc = arg.description
                wrap_text(desc, width - max - 6).each_with_index do |line, i|
                    lines << "  %-#{max}s    %s" % [[arg.to_s][i], line]
                end
            end
            lines << ''
        end
    end

    if non_positional_args?
        lines << ''
        lines << 'OPTIONS'
        lines << '-------'
        max = non_positional_args.map{ |arg| arg.to_use.length }.max
        non_positional_args.each do |arg|
            if arg.usage_break
                lines << ''
                lines << arg.usage_break
            end
            desc = arg.description
            desc += "\n[Default: #{arg.default}]" unless arg.default.nil?
            wrap_text(desc, width - max - 6).each_with_index do |line, i|
                lines << "  %-#{max}s    %s" % [[arg.to_use][i], line]
            end
        end
    end
    lines << ''

    lines.each{ |line| line.length < width ? out.puts(line) : out.print(line) } if out
    lines
end

#show_help?Boolean

Whether user indicated they would like help on supported arguments.

Returns:

  • (Boolean)

See Also:

  • Parser#show_help


498
499
500
# File 'lib/arg-parser/definition.rb', line 498

def show_help?
    parser.show_help?
end

#show_usage(out = STDERR, width = 80) ⇒ Object

Generates a usage display string



533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/arg-parser/definition.rb', line 533

def show_usage(out = STDERR, width = 80)
    lines = ['']
    usage_args = []
    usage_args.concat(positional_args.map(&:to_use))
    opt_args = size - usage_args.size
    usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
    usage_args << rest_args.to_use if rest_args?
    lines.concat(wrap_text("USAGE: #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
    lines << ''
    lines << 'Specify the /? or --help option for more detailed help'
    lines << ''
    lines.each{ |line| out.puts line } if out
    lines
end

#show_usage?Boolean

Whether user indicated they would like help on usage.

Returns:

  • (Boolean)

See Also:

  • Parser#show_usage


491
492
493
# File 'lib/arg-parser/definition.rb', line 491

def show_usage?
    parser.show_usage?
end

#validate_requirements(args) ⇒ Array

Validates the supplied args Hash object, verifying that any argument set requirements have been satisfied. Returns an array of error messages for each set requirement that is not satisfied.

Parameters:

  • args (Hash)

    a Hash containing the keys and values identified by the parser.

Returns:

  • (Array)

    a list of errors for any argument requirements that have not been satisfied.



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/arg-parser/definition.rb', line 511

def validate_requirements(args)
    errors = []
    @require_set.each do |req, set|
        count = set.count{ |arg| args.has_key?(arg.key) && args[arg.key] }
        case req
        when :one
            if count == 0
                errors << "No argument has been specified for one of: #{set.join(', ')}"
            elsif count > 1
                errors << "Only one argument can been specified from: #{set.join(', ')}"
            end
        when :any
            if count == 0
                errors << "At least one of the arguments must be specified from: #{set.join(', ')}"
            end
        end
    end
    errors
end

#wrap_text(text, width) ⇒ Array

Utility method for wrapping lines of text at width characters.

Parameters:

  • text (String)

    a string of text that is to be wrapped to a maximum width.

  • width (Integer)

    the maximum length of each line of text.

Returns:

  • (Array)

    an Array of lines of text, each no longer than width characters.



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/arg-parser/definition.rb', line 650

def wrap_text(text, width)
    if width > 0 && (text.length > width || text.index("\n"))
        lines = []
        start, nl_pos, ws_pos, wb_pos, end_pos = 0, 0, 0, 0, text.rindex(/[^\s]/)
        while start < end_pos
            last_start = start
            nl_pos = text.index("\n", start)
            ws_pos = text.rindex(/ +/, start + width)
            wb_pos = text.rindex(/[\-,.;#)}\]\/\\]/, start + width - 1)
            ### Debug code ###
            #STDERR.puts self
            #ind = ' ' * end_pos
            #ind[start] = '('
            #ind[start+width < end_pos ? start+width : end_pos] = ']'
            #ind[nl_pos] = 'n' if nl_pos
            #ind[wb_pos] = 'b' if wb_pos
            #ind[ws_pos] = 's' if ws_pos
            #STDERR.puts ind
            ### End debug code ###
            if nl_pos && nl_pos <= start + width
                lines << text[start...nl_pos].strip
                start = nl_pos + 1
            elsif end_pos < start + width
                lines << text[start..end_pos]
                start = end_pos
            elsif ws_pos && ws_pos > start && ((wb_pos.nil? || ws_pos > wb_pos) ||
                  (wb_pos && wb_pos > 5 && wb_pos - 5 < ws_pos))
                lines << text[start...ws_pos]
                start = text.index(/[^\s]/, ws_pos + 1)
            elsif wb_pos && wb_pos > start
                lines << text[start..wb_pos]
                start = wb_pos + 1
            else
                lines << text[start...(start+width)]
                start += width
            end
            if start <= last_start
                # Detect an infinite loop, and just return the original text
                STDERR.puts "Inifinite loop detected at #{__FILE__}:#{__LINE__}"
                STDERR.puts "  width: #{width}, start: #{start}, nl_pos: #{nl_pos}, " +
                            "ws_pos: #{ws_pos}, wb_pos: #{wb_pos}"
                return [text]
            end
        end
        lines
    else
        [text]
    end
end