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