Class: UsageMod::Base

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

Overview

This is the class that does the heavy lifting for the Usage class. It parses the usage string, options string and makes the information available by using method_missing.

see the README document for how to format the usage string and options string

Constant Summary collapse

DESC_PARSE_REGEX =
/^-(\w),--(\S+)\s+(.*)$/
OPTION_NAME =
1
OPTION_LONG_NAME =
2
OPTION_DESCRIPTION =
3
OPEN_PAREN_OR_BRACKET =
1
DASH_OR_TYPE =
2
ARG_OR_OPTION_NAME =
3
INFINITE =
4
CLOSE_PAREN_OR_BRACKET =
5

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(usage_ui, usageString, optionsString = "", userArguments = ARGV) ⇒ Base

create a UsageMod::Base object. usageString specifies the arguments expected. The optionsString specifies further information about the arguments. userArguments defaults to the program arguments but allows the user to get the arguments from somewhere else.



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/Usage.rb', line 595

def initialize(usage_ui, usageString, optionsString="", userArguments=ARGV)
  @usage_ui = usage_ui
  @argHash = {}
  @usageString = usageString
  @optionsString = optionsString
  @userArguments = userArguments
  @custom_args = []

  # this is what is used to support plugin parsing

  @type_chars = @@type_chars.keys.sort_by{|x| -(x.size)}.map{|x| x.gsub(/./){|s| "\\" + s}}.join("|")
  $TRACE.debug 5, "type_chars = '#{@type_chars}'"
  
  # parse the usage string

  @argList = parse_usage(usageString)

  # parse the description

  @descriptions = parse_option_descriptions

  # update options hash now that some long names may have been parsed

  @argList.update_with_long_names
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

uses the argHash to return data specified about the command line arguments by the symbol passed in.



947
948
949
950
951
952
# File 'lib/Usage.rb', line 947

def method_missing(symbol, *args)
  $TRACE.debug 5, "UsageMod::Base method missing: #{symbol}"
  if @argHash.has_key?(symbol.to_s) then
    @argHash[symbol.to_s]
  end
end

Class Method Details

.add_type_handler(type_char, klass) ⇒ Object



567
568
569
# File 'lib/Usage.rb', line 567

def add_type_handler(type_char, klass)
  @@type_chars[type_char] = klass
end

.reset_type_handlersObject



571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/Usage.rb', line 571

def reset_type_handlers
  @@type_chars = {}
  add_type_handler("@", TimeArgumentPlugin)
  add_type_handler("%", IntegerArgumentPlugin)
  add_type_handler("#", FloatArgumentPlugin)
  add_type_handler(">>?", FileAppendQueryPlugin)
  add_type_handler(">>", FileAppendPlugin)
  add_type_handler("<<", FileReadLinesPlugin)
  add_type_handler(">?", FileOutputQueryPlugin)
  add_type_handler("<", FileInputPlugin)
  add_type_handler(">", FileOutputPlugin)
  add_type_handler("<@", OpenURIInputPlugin)
end

Instance Method Details

#convert_value(arg_type, value) ⇒ Object

convert a value to its typed equivalent



897
898
899
900
901
902
903
904
905
906
907
908
909
# File 'lib/Usage.rb', line 897

def convert_value(arg_type, value)
  $TRACE.debug 5, "convert_value: #{value} to #{arg_type}"
  case arg_type.to_s
  when /^Arg:(.+)$/
    klass = @@type_chars[$1]

    $TRACE.debug 5, "got custom arg type with char '#{$1}' about to call #{klass.inspect}"
    custom_arg = klass.new(@usage_ui, value)
    @custom_args.push(custom_arg)
    return custom_arg.value
  else
    return value
  end
end

#dumpObject

dump the argument hash



937
938
939
940
941
# File 'lib/Usage.rb', line 937

def dump
  @argHash.keys.sort.each do |k|
    puts "#{k} = #{@argHash[k].inspect}"
  end
end

#long_usageObject

return the long usage string



921
922
923
924
925
926
927
928
929
930
931
932
# File 'lib/Usage.rb', line 921

def long_usage
  @optionsString.split("\n").map do |line|
    if m = DESC_PARSE_REGEX.match(line) then
      opt_str = "-#{m[OPTION_NAME]},--#{m[OPTION_LONG_NAME]}"
      opt_str + (" " * (@maxOptionNameLen + 2 - opt_str.size)) + m[OPTION_DESCRIPTION]
    elsif /^\s+(\S.*)$/.match(line) then
      (" " * (@maxOptionNameLen + 2)) + $1
    else
      line
    end
  end.join("\r\n")
end

#parse_argsObject

set up @argHash for the options and arguments based on two things (this calls UsageMod::Base#parse_options to parse the options:

# the parse usage string description in @argList (which is an ArgumentList object) # the arguments that the user ran the program with which is in @userArguments (which is an array of strings)

this allows method_missing to return the correct values when it is called



855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
# File 'lib/Usage.rb', line 855

def parse_args()
  parse_options
  $TRACE.debug 5, "parse_args: user arguments = #{@userArguments.inspect}"
  user_args_size = @userArguments.size
  req_args_size = @argList.required_arguments.size
  opt_args_size = @argList.optional_arguments.size
  $TRACE.debug 5, "user_args_size = #{user_args_size}, req_args_size = #{req_args_size}\n"
  if user_args_size < req_args_size then
    $TRACE.debug 5, "parse_args: required_arguments = #{@argList.required_arguments.inspect}"
    $TRACE.debug 5, "parse_args: optional_arguments = #{@argList.optional_arguments.inspect}"
    raise TooFewArgError.new("too few arguments #{req_args_size} expected, #{user_args_size} given")
  elsif user_args_size > (req_args_size + opt_args_size)
    if !@argList.has_infinite_arg then
      raise ExtraArgError.new("too many arguments #{req_args_size} expected, #{user_args_size} given")
    end
  end
  
  @userArguments.each_with_index do |userarg, index|
    arg = @argList.get_next_arg(index)
    $TRACE.debug 5, "userarg = '#{userarg}', arg = '#{arg}'"

    if @argList.has_infinite_arg && index + 1 >= req_args_size then
      @argHash[@argList.last_arg.name].push(userarg)
    else
      @argHash[arg.name] = convert_value(arg.arg_type, userarg)
    end
  end

  if block_given?
    begin
      yield self
    ensure
      @custom_args.each {|arg| arg.close}
    end
  end

  self
end

#parse_option_descriptionsObject

parse the option descriptions



627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/Usage.rb', line 627

def parse_option_descriptions
  @maxOptionNameLen = 0
  @optionsString.split("\n").each do |line|
    if m = DESC_PARSE_REGEX.match(line) then
      $TRACE.debug 5, "[#{m[1]}][#{m[2]}][#{m[3]}]"
      len = m[OPTION_NAME].size + m[OPTION_LONG_NAME].size + 4
      @maxOptionNameLen = len if len > @maxOptionNameLen
      if option = @argList.lookup_option(m[OPTION_NAME]) then
        option.long_name = m[OPTION_LONG_NAME]
        option.description = m[OPTION_DESCRIPTION]
      end
    end
  end
end

#parse_optionsObject

set up @argHash for all the options based on two things:

# the parse usage string description in @argList (which is an ArgumentList object) # the arguments that the user ran the program with which is in @userArguments (which is an array of strings)

this allows method_missing to return the correct values when it is called



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/Usage.rb', line 777

def parse_options()
  last_index = @userArguments.size
  found_options = []
  option_waiting_for_argument = nil
  $TRACE.debug 5, "parse_options: user arguments = #{@userArguments.inspect}"
  @userArguments.each_with_index do |userarg, index|
    $TRACE.debug 7, "arg = '#{userarg}'"      
    # if we found an option 

    if m = /^-{1,2}(.*)$/.match(userarg) then
      # break out if we are waiting for an argument

      break if option_waiting_for_argument

      # handle requests for long usage string

      if m[1] == "help" || m[1] == "?" then
        raise LongUsageRequested
      end
      
      # look up the option

      if option = @argList.lookup_option(m[1]) then
        @argHash[option.name_as_symbol] = true
        @argHash[option.long_name_as_symbol] = true if option.long_name
        found_options.push(option)
        if option.has_argument then
          option_waiting_for_argument = option
        end
      else
        raise UnknownOptionError.new("unknown option '#{userarg}'")
      end
      
    # else its either a option or regular argument

    else
      # if it is an option argument

      if option = option_waiting_for_argument then
        # process the argument

        value = convert_value(option.arg_type, userarg)
        if option.arg_type == Choices then
          if !option.choices.include?(userarg) then
            raise InvalidChoiceError.new(option, userarg)
          end
          @argHash[option.name_as_symbol] = value
          @argHash[option.long_name_as_symbol] = value if option.long_name
        else
          @argHash[option.arg_name] = value
        end
        
        option_waiting_for_argument = nil
        
      # otherwise its a regular argument 

      else
        # we need to leave this loop

        last_index = index
        break
      end
    end
  end

  if option_waiting_for_argument then
    raise MissingOptionArgumentError.new(option_waiting_for_argument)
  end
  
  missing_options = @argList.required_options - found_options
  if missing_options.size > 0 then
    raise MissingOptionsError.new(missing_options)
  end
  
  $TRACE.debug 5, "parse_options: last_index = #{last_index}"
  @userArguments = @userArguments[last_index..-1]
end

#parse_usage(usageString) ⇒ Object

this parses the usage string and returns an ArgumentList object that describes the arguments by their characteristics (type, optional/required, etc)

Raises:



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
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
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'lib/Usage.rb', line 653

def parse_usage(usageString)
  type_chars = @type_chars + "|\-"
  #                 ([      @%#<><<>>      name   ...      )]

  parse_regex = /^([\(\[])?(#{type_chars})?([^.\)\]]*)(\.\.\.)?([\)\]])?$/
  $TRACE.debug 5, "whole regex = #{parse_regex.source}"
  
  arg_list = ArgumentList.new
  option_stack = []
  processing_options = true
  option = nil
  choices = nil
  usageString.split(/\s/).each do |arg|
    $TRACE.debug 5, "arg = '#{arg}'"
    m = parse_regex.match(arg)
    infinite = false
    optional_arg = false
    option = nil
    if m then
      $TRACE.debug 5, "got match, opening='#{m[OPEN_PAREN_OR_BRACKET]}' " +
                 "dash_or_type='#{m[DASH_OR_TYPE]}' " + 
                 "main='#{m[ARG_OR_OPTION_NAME]}' " + 
                 "infinite = '#{m[INFINITE]}' " +
                 "closing='#{m[CLOSE_PAREN_OR_BRACKET]}'"
      $TRACE.debug 9, "option = #{option.inspect}, option_stack = #{option_stack.inspect}"

      choices = m[ARG_OR_OPTION_NAME].split(/\|/)
      raise ChoicesNotOnOptionError.new if choices.size > 1 && option_stack.size == 0
      
      if m[DASH_OR_TYPE] then
        raise ChoiceNoTypeError.new if choices.size > 1
        
        case m[DASH_OR_TYPE]
        when "-"
          option = Option.new(m[ARG_OR_OPTION_NAME])
          if m[OPEN_PAREN_OR_BRACKET] then
            option.is_required = (m[OPEN_PAREN_OR_BRACKET] == "(")
            option_stack.push(option)
          else
            option.arg_type = TrueClass
          end
        else
          arg_type = "Arg:#{m[DASH_OR_TYPE]}"
          $TRACE.debug 5, "is custom type '#{m[DASH_OR_TYPE]}'"
        end
      else
        arg_type = String
      end

      if m[CLOSE_PAREN_OR_BRACKET] then 
        if option_stack.size == 0 then
          if m[OPEN_PAREN_OR_BRACKET] && m[DASH_OR_TYPE] != '-' then
            # Assume this is an optional parameter

            optional_arg = true
            $TRACE.debug 9, "parse_usage: Optional paramater found: #{m[ARG_OR_OPTION_NAME]}"
          else
            raise NestingUnderflow.new
          end
        else
          option = option_stack.pop
          if option.is_required != (m[CLOSE_PAREN_OR_BRACKET] == ")") then
            raise MismatchedBracesError.new
          end
          # it has an argument if this token didn't also have a open bracket

          option.has_argument = !m[OPEN_PAREN_OR_BRACKET]
          if option.has_argument then
            if choices.size > 1 then
              option.choices = Choices.new(choices)
              option.arg_type = Choices
            else
              option.arg_name = m[ARG_OR_OPTION_NAME]
              option.arg_type = arg_type
            end
          end
        end
      end
      
      if m[INFINITE] != nil then
        $TRACE.debug 5, "is infinite"
        infinite = true
      end

      $TRACE.debug 9, "option = #{option.inspect}, option_stack = #{option_stack.inspect}"
      unless option || option_stack.size > 0
        $TRACE.debug 9, "no longer processing options"
        processing_options = false
      end
    else
      arg_type = String
      $TRACE.debug 5, "is string"
    end

    if processing_options then
      if option_stack.size == 0 then
        $TRACE.debug 5, "adding option #{option.name}"
        arg_list.push_option(option)
      end
    else
      arg = m[ARG_OR_OPTION_NAME]
      if infinite then
        @argHash[arg] = []
      else
        @argHash[arg] = nil
      end
      $TRACE.debug 5, "adding argument '#{arg}'"
      arg_list.push_arg(Argument.new(arg, arg_type, infinite, optional_arg))

      arg_list.process
    end
  end

  raise NestingOverflow.new if option_stack.size > 0
  
  $TRACE.debug 9, "arg_list = #{arg_list.inspect}\n"
  arg_list
end

#usageObject

return the short usage string



914
915
916
# File 'lib/Usage.rb', line 914

def usage
  @usageString.gsub(/#{@type_chars}/, "") + "\n"
end