Class: Methadone::OptionParserProxy

Inherits:
Object
  • Object
show all
Defined in:
lib/methadone/main.rb

Overview

Methadone Internal - treat as private

A proxy to OptionParser that intercepts #on so that we can allow a simpler interface

Instance Method Summary collapse

Constructor Details

#initialize(option_parser, options) ⇒ OptionParserProxy

Create the proxy

option_parser

An OptionParser instance

options

a hash that will store the options set via automatic setting. The caller should retain a reference to this



589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/methadone/main.rb', line 589

def initialize(option_parser,options)
  @option_parser = option_parser
  @options = options
  @option_defs ||= {:local => [],:global => []} 
  @option_sigs = {}
  @options_used = []
  @usage_rules = {}
  @commands = {}
  @selected_command = nil
  @user_specified_banner = false
  @accept_options = false
  @args = []
  @arg_options = {}
  @arg_filters = {}
  @arg_documentation = {}
  @args_by_name = {}
  @description = nil
  @version = nil
  @banner_stale = true
  document_help
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Defers all calls save #on to the underlying OptionParser instance



819
820
821
# File 'lib/methadone/main.rb', line 819

def method_missing(sym,*args,&block)
  @option_parser.send(sym,*args,&block)
end

Instance Method Details

#arg(arg_name, *options) ⇒ Object

Sets the banner to include these arg names



799
800
801
802
803
804
805
806
807
808
# File 'lib/methadone/main.rb', line 799

def arg(arg_name,*options)
  options << :optional if options.include?(:any) && !options.include?(:optional)
  options << :required unless options.include? :optional
  options << :one unless options.include?(:any) || options.include?(:many)
  @args << arg_name
  @arg_options[arg_name] = options
  @arg_documentation[arg_name]= options.select(&STRINGS_ONLY)
  @arg_filters[arg_name] = options.select {|o| o.is_a?(Array) or o.is_a?(Range) or o.is_a?(::Regexp)}
  @banner_stale = true
end

#args_for_mainObject



703
704
705
# File 'lib/methadone/main.rb', line 703

def args_for_main
  @args.map {|name| @args_by_name[name]}
end


823
824
825
826
# File 'lib/methadone/main.rb', line 823

def banner
  set_banner if @banner_stale
  @option_parser.banner
end

#banner=(new_banner) ⇒ Object

Proxies to underlying OptionParser



792
793
794
795
# File 'lib/methadone/main.rb', line 792

def banner=(new_banner)
  @option_parser.banner=new_banner
  @user_specified_banner = true
end

#check_args!Object



633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
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
699
700
701
# File 'lib/methadone/main.rb', line 633

def check_args!
  arg_allocation_map = @args.map {|arg_name| @arg_options[arg_name].include?(:required) ? 1 : 0} 
  
  arg_count = ::ARGV.length - arg_allocation_map.reduce(0,&:+)
  if arg_count > 0
    @args.each.with_index do |arg_name,i|
      if (@arg_options[arg_name] & [:many,:any]).length > 0
        arg_allocation_map[i] += arg_count
        break
      elsif @arg_options[arg_name].include? :optional
        arg_allocation_map[i] += 1
        arg_count -= 1
        break if arg_count == 0
      end
    end
  end

  @args.zip(arg_allocation_map).each do |arg_name,arg_count|
    if not (@arg_options[arg_name] & [:many,:any]).empty?
      arg_value = ::ARGV.shift(arg_count)
    else
      arg_value = (arg_count == 1) ? ::ARGV.shift : nil
    end

    if @arg_options[arg_name].include? :required and arg_value.nil?
      message = "'#{arg_name.to_s}' is required"
      raise ::OptionParser::ParseError,message
    elsif @arg_options[arg_name].include?(:many) and arg_value.empty? 
      message = "at least one '#{arg_name.to_s}' is required"
      raise ::OptionParser::ParseError,message
    end

    unless arg_value.nil? or arg_value.empty? or @arg_filters[arg_name].empty?
      match = false
      msg = ''
      @arg_filters[arg_name].each do |filter| 
        if not (@arg_options[arg_name] & [:many,:any]).empty?
          if filter.respond_to? :include?
            invalid_values = (filter | arg_value) - filter
          elsif filter.is_a? ::Regexp
            invalid_values = arg_value - arg_value.grep(filter)
          end
          if invalid_values.empty?
            match = true
            break
          end
          msg = "The following value(s) were invalid: '#{invalid_values.join(' ')}'"
        else
          if filter.respond_to? :include?
            if filter.include? arg_value
              match = true
              break
            end
          elsif filter.is_a?(::Regexp)
            if arg_value =~ filter
              match = true
              break
            end
          end
          msg = "'#{arg_value}' is invalid"
        end
      end

      raise ::OptionParser::ParseError, "#{arg_name}: #{msg}" unless match

    end
    @args_by_name[arg_name] = arg_value
  end
end

#check_option_usage!Object



763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'lib/methadone/main.rb', line 763

def check_option_usage!
  requirers = @options_used.select {|name| @usage_rules.fetch(name,{}).key?(:requires)}
  requirers.each do |name|
    required = [@usage_rules[name][:requires]].flatten
    violation = required - @options_used
    unless violation.empty?
      raise OptionParser::OptionConflict.new("Missing option #{@option_sigs[violation.first]} required by option #{@option_sigs[name]}")
    end
  end
  excluders = @options_used.select {|name| @usage_rules.fetch(name,{}).key?(:excludes)}
  excluders.each do |name|
    excluded = [@usage_rules[name][:excludes]].flatten
    violation = (excluded & @options_used)
    unless violation.empty?
      raise OptionParser::OptionConflict, "#{@option_sigs[name]} cannot be used if already using #{@option_sigs[violation.first]}"
    end
  end
end

#command(provider_hash = {}) ⇒ Object

Specify an acceptable command that will be hanlded by the given command provider



783
784
785
786
787
788
789
# File 'lib/methadone/main.rb', line 783

def command(provider_hash={})
  provider_hash.each do |name,cls|
    raise InvalidProvider.new("Provider for #{name} must respond to go!") unless cls.respond_to? :go!
    commands[name.to_s] = cls
  end
  @banner_stale = true
end

#command_namesObject

List the command names



863
864
865
# File 'lib/methadone/main.rb', line 863

def command_names
  @command_names ||= commands.keys.map {|k| k.to_s}
end

#commandsObject

Acess the command provider list



840
841
842
# File 'lib/methadone/main.rb', line 840

def commands
  @commands
end

#description(desc) ⇒ Object



810
811
812
813
814
815
# File 'lib/methadone/main.rb', line 810

def description(desc)

  @description = desc if desc
  @banner_stale = true
  @description
end

#extend_help_from_parent(parent_opts) ⇒ Object



898
899
900
901
# File 'lib/methadone/main.rb', line 898

def extend_help_from_parent(parent_opts)
  self.parent_opts = parent_opts
  @banner_stale = true
end

#global_optionsObject



619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/methadone/main.rb', line 619

def global_options
  global_option_defs = @option_defs.fetch(:global, nil)
  return {} if global_option_defs.nil?

  keys = global_option_defs.map {|opt_def|
    [opt_def.long, opt_def.short].
      flatten.
      map {|flag| flag.sub(/^--?(\[no-\])?/,'')}.
      map {|flag| [flag,flag.to_sym]}
  }.flatten
  global_hash = @options.select {|k,v| keys.include? k}
  global_hash.is_a?(Array) ? Hash[global_hash] : global_hash # Stupid 1.8.7 => 1.9.3 API change of Hash#select
end

#helpObject



828
829
830
831
# File 'lib/methadone/main.rb', line 828

def help
  set_banner if @banner_stale
  @option_parser.to_s
end

#on(*args, &block) ⇒ Object

If invoked as with OptionParser, behaves the exact same way. If invoked without a block, however, the options hash given to the constructor will be used to store the parsed command-line value. See #opts in the Main module for how that works. Returns reference to the option for exclusive and mutual



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
742
743
744
745
# File 'lib/methadone/main.rb', line 713

def on(*args,&block)

  # Group together any of the hash arguments
  (hashes, args) = args.partition {|a| a.respond_to?(:keys)}
  on_opts = hashes.reduce({}) {|h1,h2| h1.merge(h2)}

  scope = args.delete(:global) || :local
  args = add_default_value_to_docstring(*args)
  sig = option_signature(args)
  opt_names = option_names(*args)

  opt_names.each do |name|
    @option_sigs[name] = sig
  end

  block ||= Proc.new do |value|
    opt_names.each do |name| 
      @options[name] = value
    end
  end
  wrapper = Proc.new do |value|
    register_usage opt_names
    block.call(value)
  end

  opt = @option_parser.define(*args,&wrapper)
  @option_defs[scope] << opt

  set_usage_rules_for(opt_names,on_opts)

  @accept_options = true
  @banner_stale = true
end

#parent_optsObject



615
616
617
# File 'lib/methadone/main.rb', line 615

def parent_opts
  @parent_opts || nil
end

#parent_opts=(parent_opts) ⇒ Object



611
612
613
# File 'lib/methadone/main.rb', line 611

def parent_opts=(parent_opts)
  @parent_opts = parent_opts
end

#parse_to_command!Object



844
845
846
847
848
849
# File 'lib/methadone/main.rb', line 844

def parse_to_command!
  @option_parser.order!
  if command_names.include? ::ARGV[0]
    @selected_command = ::ARGV.shift
  end
end

#post_setupObject

We need some documentation to appear at the end, after all OptionParser setup has occured, but before we actually start. This method serves that purpose



869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/methadone/main.rb', line 869

def post_setup
  if parent_opts and not (global_opts = parent_opts.global_options_help).empty?
    @option_parser.separator ''
    global_opts.split("\n").each {|line| @option_parser.separator line}
  end

  if @commands.empty? and ! @arg_documentation.empty?
    @option_parser.separator ''
    @option_parser.separator "Arguments:"
    @args.each do |arg|
      option_tag = @arg_options[arg].include?(:optional) ? ' (optional)' : ''
      @option_parser.separator "    #{arg}#{option_tag}"
      @arg_documentation[arg].each do |doc|
        @option_parser.separator "        #{doc}"
      end
    end
  end

  unless @commands.empty?
    padding = @commands.keys.map {|name| name.to_s.length}.max + 1
    @option_parser.separator ''
    @option_parser.separator "Commands:"
    @commands.each do |name,provider|
      @option_parser.separator "  #{ "%-#{padding}s" % (name.to_s+':')} #{provider.description}"
    end
  end
  @option_parser.separator ''
end

#register_usage(opt_names) ⇒ Object



757
758
759
760
761
# File 'lib/methadone/main.rb', line 757

def register_usage(opt_names)
  opt_names.each do |name| 
    @options_used << name
  end
end

#selected_commandObject

The selected command



852
853
854
# File 'lib/methadone/main.rb', line 852

def selected_command
  @selected_command
end

#set_usage_rules_for(names, rules_source) ⇒ Object



747
748
749
750
751
752
753
754
755
# File 'lib/methadone/main.rb', line 747

def set_usage_rules_for(names,rules_source)
  rule_keys = [:excludes, :requires]
  rules = Hash[rule_keys.zip(rules_source.values_at(*rule_keys))].reject{|k,v| v.nil?}
  return if rules.empty?

  names.each do |name|
    @usage_rules[name] = rules
  end
end

#to_sObject

Since we extend Object on 1.8.x, to_s is defined and thus not proxied by method_missing



834
835
836
# File 'lib/methadone/main.rb', line 834

def to_s #::nodoc::
  help
end

#version(version) ⇒ Object

Sets the version for the banner



857
858
859
860
# File 'lib/methadone/main.rb', line 857

def version(version)
  @version = version
  @banner_stale = true
end