File: option.rb

package info (click to toggle)
libcommandline-ruby 0.7.10-10
  • links: PTS
  • area: main
  • in suites: lenny, squeeze
  • size: 552 kB
  • ctags: 289
  • sloc: ruby: 2,881; makefile: 7
file content (185 lines) | stat: -rw-r--r-- 6,053 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#  $Id: option.rb,v 1.1.1.1 2005/09/08 15:51:38 jdf Exp $
#  $Source: /Users/jdf/projects/CVSROOT/devel/ruby/commandline/lib/commandline/optionparser/option.rb,v $
#
#  Author: Jim Freeze
#  Copyright (c) 2005 Jim Freeze
#
# =DESCRIPTION
# A very flexible commandline parser
#
# =Revision History
#  Jim.Freeze 04/01/2005 Birthday
#

module CommandLine

class Option
  class OptionError < StandardError; end
  class InvalidOptionNameError < OptionError; end
  class InvalidArgumentError < OptionError; end
  class MissingOptionNameError < OptionError; end
  class InvalidArgumentArityError < OptionError; end
  class MissingPropertyError < OptionError; end
  class InvalidPropertyError < OptionError; end
  class InvalidConstructionError < OptionError; end

  attr_accessor :posix

  #
  GENERAL_OPT_EQ_ARG_RE  = /^(-{1,2}[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/  # :nodoc:
  GNU_OPT_EQ_ARG_RE      = /^(--[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/
  #OPTION_RE              = /^-{1,2}([a-zA-Z]+\w*)(.*)/
  #UNIX_OPT_EQ_ARG_RE     = /^(-[a-zA-Z])=(.*)$/
  #UNIX_OPT_EQorSP_ARG_RE = /^(-[a-zA-Z])(=|\s+)(.*)$/

  POSIX_OPTION_RE     = /^-[a-zA-Z]?$/
  # need to change this to support - and --
  NON_POSIX_OPTION_RE = /^(-|-{1,2}[a-zA-Z_]+[-_a-zA-Z0-9]*)/

  PROPERTIES = [ :names, :arity, :opt_description, :arg_description,
                 :opt_found, :opt_not_found, :posix
               ]

  FLAG_BASE_OPTS = {
    :arity       => [0,0],
#    :opt_description => nil,
    :arg_description => "",
    :opt_found       => true,
    :opt_not_found   => false
  }

  # You get these without asking for them
  DEFAULT_OPTS = {
    :arity       => [1,1],
    :opt_description => "",
    :arg_description => "",
    :opt_found       => true,  #TODO: Should be get_args?
    :opt_not_found   => false
  }

  #
  # Option.new(:flag, :posix => true, :names => %w(--opt))
  #
  # TODO: Should we test and raise key is not one of :names, opt_description, ...
  # This will prevent typos. Can users add properties to an Option that are their own?
  def initialize(*all)
    @posix = false

    raise(MissingPropertyError,
      "No properties specified for new #{self.class}.") if all.empty?

    until Hash === all[0]
      case (prop = all.shift)
      when :flag then @flag = true
      when :posix then @posix = true
      else 
        raise(InvalidPropertyError, "Unknown option setting '#{prop}'.")
      end
    end

    unknown_keys = all[0].keys.find_all { |k| !PROPERTIES.include?(k) && /^use?r_/ !~ "#{k}" }
    raise(InvalidPropertyError, 
      "The key #{unknown_keys.inspect} is not known and is not a user key.") unless 
        unknown_keys.empty?
  
    @flag = nil unless defined?(@flag)
    type = @flag.nil? ? :default : :flag
    merge_hash = 
      case type
      when :flag then FLAG_BASE_OPTS
      when :default then DEFAULT_OPTS
      else raise(InvalidConstructionError, 
        "Invalid arguments to Option.new. Must be a property hash with "+
        "keys [:names, :arity, :opt_description, :arg_description, "+
        ":opt_found, :opt_not_found] or "+
        "an option type [:flag, :default].")
      end

    @properties = {}.merge(merge_hash)
    all.each { |properties|
      raise(InvalidPropertyError, 
        "Don't understand argument of type '#{properties.class}' => "+
            "#{properties.inspect} passed to #{self.class}.new. Looking "+
            "for type Hash.") unless properties.kind_of?(Hash)

      @properties.merge!(properties)
    }
    
    @properties[:names] = [@properties[:names]].flatten.compact

    arg_arity = @properties[:arity]
    @properties[:arity] = [arg_arity, arg_arity] unless 
      arg_arity.kind_of?(Array)

    raise "Invalid value for arity '#{arg_arity}'." unless 
      arg_arity.kind_of?(Array) || arg_arity.kind_of?(Fixnum)

    raise(InvalidArgumentArityError,
      "Conflicting value given to new option: :flag "+
      "and :arity = #{@properties[:arity].inspect}.") if 
        :flag == type && [0,0] != @properties[:arity]

    names = @properties[:names]
    raise(MissingOptionNameError, 
      "Attempt to create an Option without :names defined.") if 
      names.nil? || names.empty?

    names.each { |name| check_option_name(name) }
    validate_arity @properties[:arity]

    create_opt_description if :flag == type
  end

  def create_opt_description
    return if @properties.has_key?(:opt_description)
    word = @properties[:names].grep(/^--\w.+/)
    if word.empty?
      @properties[:opt_description] = ""
    else
      @properties[:opt_description] = "Sets #{word.first} to true."
    end
  end

  def check_option_name(name)
    raise(InvalidOptionNameError, 
      "Option name '#{name}' contains invalid space.") if /\s+/.match(name)

    if @posix
      raise(InvalidOptionNameError, 
        "Option name '#{name}' is invalid.") unless POSIX_OPTION_RE.match(name)
    else
      raise(InvalidOptionNameError, 
        "Option name '#{name}' is invalid.") unless NON_POSIX_OPTION_RE.match(name)
    end
  end

  def validate_arity(arity)
  raise ":arity is nil" if arity.nil?
    min, max = *arity

    raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
      "greater than or equal to 0.") unless min >= 0
    raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
      "greater than or equal to -1.") if max < -1
    raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
      "greater than minimum arity '#{min}'.") if max < min && max != -1
    if @posix
      raise(InvalidArgumentArityError, "Posix options only support :arity "+
        "of [0,0] or [1,1].") unless ([0,0] == arity) || ([1,1] == arity)
    end
  end

  def method_missing(sym, *args)
    raise "Unknown property '#{sym}' for option 
      #{@properties[:names].inspect unless @properties[:names].nil?}." unless 
        @properties.has_key?(sym) || PROPERTIES.include?(sym)
    @properties[sym, *args]
  end

  def to_hash
    Marshal.load(Marshal.dump(@properties))
  end

end#class Option

end#module CommandLine