
=begin
    
    > prog --size 1024   IMG1.JPG  IMG2.JPG  IMG3.JPG
    
    #
    # Lazy method
    #
    args = CommandLineArgs.new(ARGV)
    s = args["size"]            # => "1024"



    > prog --size 1024  --no-verbose  IMG1.JPG  IMG2.JPG  IMG3.JPG
    #
    # Standard method that included documentation
    #
    args = CommandLineArgs.new(ARGV) do
        option "--size", "-s", "Size of the image."
        flag   "--[no-]verbose", "-v", "Enable verbose output."
    end
    
    s = args["size"]            # => "1024"
    s = args["s"]               # => "1024"
    v = args["verbose"]         # => nil
    files = ARGV                # => ["IMG1.JPG",  "IMG2.JPG",  "IMG3.JPG"]
    files = args.unused         # => ["IMG1.JPG",  "IMG2.JPG",  "IMG3.JPG"]

    puts args.usage # =>
"
      --size,-s
          Size of the image.

      --[no-]verbose,-v
          Enable verbose output.
"
=end

require 'delegate'



LONG_OPT_RE = /^--([[:alnum:]].*)/ 
SHORT_OPT_RE= /^-([[:alnum:]])$/
NO_FLAG_RE  = /--\[([[:alnum:]].*)\](.*)/

class CommandLineArgs < Hash
    
    attr_accessor   :unused, :options
    
    def initialize(args_list, &block)
        raise ArgumentError, "Argument list must be an array"  unless args_list.kind_of? Array
        
        h = process_args args_list
        @options = h[:options]              # array of option-value pairs
        @unused  = h[:args]                 # command line arguments after the last option
        args_list.replace @unused
        
        @options.each{|k,v| self[k] = v }
    end
   

    def initialize_without_block(args_list)
    end
    
    
    def process_args(args_list)
        return { :options=>[], :args=>[] }  if args_list.nil?  || args_list == []
        
        arg = args_list.first
        
        case arg
        when LONG_OPT_RE , SHORT_OPT_RE
            option_name = $1
            return process_option_and_args( option_name, rest(args_list) )
        else
            return { :options=>[], :args=>args_list }
        end
    end


    def process_option_and_args( option_name, args_list )
        arg = args_list.first
        
        case arg
        when nil
            raise ArgumentError, "Missing value for option #{option_name}"
        else
            h = process_args rest(args_list)
            return {
                :options => [ [ option_name, arg ] ] + h[:options],
                :args    => h[:args]
            }
        end
    end
    #
    # Utilities
    #
    
    def rest(arr)
        return arr[1..-1]  if arr.length > 1
    end
end


class CommandLineOptions < SimpleDelegator

    attr_accessor   :unused
    #
    #
    # Initializing with a block
    #
    #

    def initialize(args_list, &block)
        
        opt = OptionDSL.new
        opt.instance_eval &block
        
        @unused = process_args_strict args_list, opt
        args_list.replace @unused
        
        super   ( Hash.new{|hash,key| 
                    if opt.has_key?(key) 
                        opt[key][:value] 
                    else
                        raise ArgumentError, "Unlisted option"
                    end
                })
    end

    

    def process_args_strict(args_list, opt)
        return  if args_list.nil?  || args_list == []
        
        arg = args_list.first
        
        case arg
        when LONG_OPT_RE , SHORT_OPT_RE
        
            option_name = $1
            option_hash = opt[option_name]

            if option_hash
                case option_hash[:type]
                when :option
                    return process_option_and_args_strict( rest(args_list), opt, option_hash )
                when :flag
                    option_hash[:value] = true
                    return process_args_strict(rest(args_list), opt)
                when :noflag
                    option_hash[:yes_flag][:value] = false
                    return process_args_strict(rest(args_list), opt)
                else
                    raise Exception, "Unexpected programming error"
                end
                
            else 
                raise ArgumentError, "Unrecognized option #{option_name}"
            end
        else
            return args_list
        end
    end

    def process_option_and_args_strict( args_list, opt, option_hash )
        arg = args_list.first
        
        raise ArgumentError, "Missing value for option #{option_name}"  unless arg

        option_hash[:value] = arg
        return process_args_strict( rest(args_list), opt )
    end

    #
    # Utilities
    #
    
    def rest(arr)
        return arr[1..-1]  if arr.length > 1
    end

end



class OptionDSL < Hash
    
    attr_accessor       :option_list
    
    def initialize
        @option_list = []
    end
    
    def option(*specs)
        h = parse_option_spec specs
        h[:type]        = :option
        h[:required]    = false
        @option_list << h
    end

    def required_option(*specs)
        h = parse_option_spec specs
        h[:type]        = :option
        h[:required]    = true
        @option_list << h
    end

    def flag(*specs)
        h = parse_option_spec specs
        h[:type]        = :flag
        h[:required]    = false
        @option_list << h
    end

    def required_flag(*specs)
        h = parse_option_spec specs
        h[:type]        = :flag
        h[:required]    = true
        @option_list << h
    end

    def parse_option_spec( specs )
        h = { :option_names => [], :comments => [] }

        specs.each{|spec|
            case spec
            when LONG_OPT_RE , SHORT_OPT_RE
                option_name = $1
                if self[option_name]
                    raise ArgumentError, "Duplicate option #{option_name}"
                end
                h[:option_names] << option_name
                self[option_name] = h

            when NO_FLAG_RE
                no, option_name = $1, $2
                raise ArgumentError, "Duplicate option #{option_text}" if self[option_name]
                raise ArgumentError, "Duplicate option #{no+option_name}" if self[no+option_name]

                h[:option_names] << option_name
                self[option_name] = h
                
                g = {:type=>:noflag, :required=>false, :yes_flag=>h}
                self[no+option_name] = g
            else
                h[:comments] << spec
            end
        }

        return h
    end
end


if __FILE__ == $0
    
require "test/unit"
require "pp"

class TestOptionDSL < Test::Unit::TestCase
    def test_option
        opt = OptionDSL.new
        opt.instance_eval {
            option "--size", "-s", "Size of the image."
            flag   "--[no-]verbose", "-v", "Enable verbose output."
        }

        h = opt
        
        assert_equal ["Size of the image."], h["size"][:comments]
        assert_equal ["Size of the image."], h["s"][:comments]
        assert_equal :option,                h["size"][:type]
        assert_equal ["Enable verbose output."], h["verbose"][:comments]
        assert_equal ["Enable verbose output."], h["no-verbose"][:yes_flag][:comments]
        assert_equal ["Enable verbose output."], h["v"][:comments]
    end
end



class TestOptions < Test::Unit::TestCase
    
    def test_no_option_no_args
        argv = []
        args = CommandLineArgs.new argv
        assert_equal nil, args["size"]
        assert_equal [],  args.unused
        assert_equal [],  argv
    end
    
    
    def test_args_only
        argv = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineArgs.new argv
        assert_equal nil, args["size"]
        assert_equal expected_args,  args.unused
        assert_equal expected_args,  argv
    end


    def test_args_and_option
        argv = %w{ --size 1024  IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineArgs.new argv
        assert_equal( {"size"=>"1024"}, args )
        assert_equal "1024", args["size"]
        assert_equal expected_args,  args.unused
        assert_equal expected_args,  argv
    end

    def test_option_only
        argv = %w{ --size 1024 }
        expected_args = []
        args = CommandLineArgs.new argv
        assert_equal "1024", args["size"]
        assert_equal nil   , args["1024"]
        assert_equal expected_args,  args.unused
        assert_equal expected_args,  argv
    end

    def test_option_block
        argv = %w{ --size 1024  IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineOptions.new argv do 
            option "--size", "-s", "Size of the image."
            flag   "--[no-]verbose", "-v", "Enable verbose output."
        end

        assert_equal "1024", args["size"]
        assert_equal "1024", args["s"]
        assert ! args["v"]
        assert ! args["verbose"]
        assert_raises(ArgumentError) do args["unlisted-parameter"] end
        assert_equal expected_args,  args.unused
    end

    def test_flag_block
        argv = %w{ --size 1024 -v IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineOptions.new argv do 
            option "--size", "-s", "Size of the image."
            flag   "--[no-]verbose", "-v", "Enable verbose output."
        end

        assert_equal "1024", args["size"]
        assert_equal "1024", args["s"]
        assert_equal true,   args["v"]
        # assert ! args["[no-]verbose"]
        assert_raises(ArgumentError) do args["unlisted-parameter"] end
        assert_equal expected_args,  args.unused
    end

    def test_no_flag
        argv = %w{ --size 1024 --no-verbose IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineOptions.new argv do 
            option "--size", "-s", "Size of the image."
            flag   "--[no-]verbose", "-v", "Enable verbose output."
        end

        assert_equal "1024", args["size"]
        assert_equal "1024", args["s"]
        assert_equal false,  args["v"]
        assert_equal false,  args["verbose"]
        assert_raises(ArgumentError) do args["unlisted-parameter"] end
        assert_equal expected_args,  args.unused
    end
    
    def test_unspecified_option_block
        argv = %w{ IMG1.JPG  IMG2.JPG  IMG3.JPG}
        expected_args = %w{IMG1.JPG  IMG2.JPG  IMG3.JPG}
        args = CommandLineOptions.new argv do 
            option "--size", "-s", "Size of the image."
            flag   "--[no-]verbose", "-v", "Enable verbose output."
        end

        assert_equal nil, args["size"]
        assert_equal nil, args["s"]
        assert ! args["v"]
        assert ! args["verbose"]
        assert_raises(ArgumentError) do args["unlisted-parameter"] end
        assert_equal expected_args,  args.unused
    end

    def test_duplicate_option
        assert_raises(ArgumentError) do
            args = CommandLineOptions.new [] do 
                option "--size", "-s", "Size of the image."
                flag   "--size", "-v", "Duplicate."
            end
        end
    end


end

    
end








