Class: Getopt::Long

Inherits:
Object
  • Object
show all
Includes:
Version
Defined in:
lib/getopt/long.rb

Overview

The Getopt::Long class encapsulates longhanded parameter parsing options

Defined Under Namespace

Classes: Error

Constant Summary

Constants included from Version

Version::VERSION

Class Method Summary collapse

Class Method Details

.getopts(*switches) ⇒ Object

Takes an array of switches. Each array consists of up to three elements that indicate the name and type of switch. Returns a hash containing each switch name, minus the ‘-’, as a key. The value for each key depends on the type of switch and/or the value provided by the user.

The long switch must be provided. The short switch defaults to the first letter of the short switch. The default type is BOOLEAN.

Example:

opts = Getopt::Long.getopts(
   ['--debug'                   ],
   ['--verbose', '-v'           ],
   ['--level',   '-l', INCREMENT]
)

See the README file for more information.



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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/getopt/long.rb', line 41

def self.getopts(*switches)
   if switches.empty?
      raise ArgumentError, 'no switches provided'
   end

   hash  = {} # Hash returned to user
   valid = [] # Tracks valid switches
   types = {} # Tracks argument types
   syns  = {} # Tracks long and short arguments, or multiple shorts

   # If a string is passed, split it and convert it to an array of arrays
   if switches.first.kind_of?(String)
      switches = switches.join.split
      switches.map!{ |switch| switch = [switch] }
   end

   # Set our list of valid switches, and proper types for each switch
   switches.each{ |switch|
      valid.push(switch[0])       # Set valid long switches

      # Set type for long switch, default to BOOLEAN.
      if switch[1].kind_of?(Integer)
         switch[2] = switch[1]
         types[switch[0]] = switch[2]
         switch[1] = switch[0][1..2]
      else
         switch[2] ||= BOOLEAN
         types[switch[0]] = switch[2]
         switch[1] ||= switch[0][1..2]
      end

      # Create synonym hash.  Default to first char of long switch for
      # short switch, e.g. "--verbose" creates a "-v" synonym.  The same
      # synonym can only be used once - first one wins.
      syns[switch[0]] = switch[1] unless syns[switch[1]]
      syns[switch[1]] = switch[0] unless syns[switch[1]]

      switch[1] = [switch[1]]

      switch[1].each{ |char|
         types[char] = switch[2]  # Set type for short switch
         valid.push(char)         # Set valid short switches
      }
   }

   re_long     = /^(--\w+[-\w+]*)?$/
   re_short    = /^(-\w)$/
   re_long_eq  = /^(--\w+[-\w+]*)?=(.*?)$|(-\w?)=(.*?)$/
   re_short_sq = /^(-\w)(\S+?)$/

   ARGV.each_with_index{ |opt, index|

      # Allow either -x -v or -xv style for single char args
      if re_short_sq.match(opt)
         chars = opt.split("")[1..-1].map{ |s| s = "-#{s}" }

         chars.each_with_index{ |char, i|
            unless valid.include?(char)
               raise Error, "invalid switch '#{char}'"
            end

            # Grab the next arg if the switch takes a required arg
            if types[char] == REQUIRED
               # Deal with a argument squished up against switch
               if chars[i+1]
                  arg = chars[i+1..-1].join.tr('-', '')
                  ARGV.push(char, arg)
                  break
               else
                  arg = ARGV.delete_at(index+1)
                  if arg.nil? || valid.include?(arg) # Minor cheat here
                     err = "no value provided for required argument '#{char}'"
                     raise Error, err
                  end
                  ARGV.push(char, arg)
               end
            elsif types[char] == OPTIONAL
               if chars[i+1] && !valid.include?(chars[i+1])
                  arg = chars[i+1..-1].join.tr("-","")
                  ARGV.push(char, arg)
                  break
               elsif
                  if ARGV[index+1] && !valid.include?(ARGV[index+1])
                     arg = ARGV.delete_at(index+1)
                     ARGV.push(char, arg)
                  end
               else
                  ARGV.push(char)
               end
            else
               ARGV.push(char)
            end
         }
         next
      end

      if match = re_long.match(opt) || match = re_short.match(opt)
         switch = match.captures.first
      end

      if match = re_long_eq.match(opt)
         switch, value = match.captures.compact
         ARGV.push(switch, value)
         next
      end

      # Make sure that all the switches are valid.  If 'switch' isn't
      # defined at this point, it means an option was passed without
      # a preceding switch, e.g. --option foo bar.
      unless valid.include?(switch)
         switch ||= opt
         raise Error, "invalid switch '#{switch}'"
      end

      # Required arguments
      if types[switch] == REQUIRED
         nextval = ARGV[index+1]

         # Make sure there's a value for mandatory arguments
         if nextval.nil?
            err = "no value provided for required argument '#{switch}'"
            raise Error, err
         end

         # If there is a value, make sure it's not another switch
         if valid.include?(nextval)
            err = "cannot pass switch '#{nextval}' as an argument"
            raise Error, err
         end

         # If the same option appears more than once, put the values
         # in array.
         if hash[switch]
            hash[switch] = [hash[switch], nextval].flatten
         else
            hash[switch] = nextval
         end
         ARGV.delete_at(index+1)
      end

      # For boolean arguments set the switch's value to true.
      if types[switch] == BOOLEAN
         if hash.has_key?(switch)
            raise Error, 'boolean switch already set'
         end
         hash[switch] = true
      end

      # For increment arguments, set the switch's value to 0, or
      # increment it by one if it already exists.
      if types[switch] == INCREMENT
         if hash.has_key?(switch)
            hash[switch] += 1
         else
            hash[switch] = 1
         end
      end

      # For optional argument, there may be an argument.  If so, it
      # cannot be another switch.  If not, it is set to true.
      if types[switch] == OPTIONAL
         nextval = ARGV[index+1]
         if valid.include?(nextval)
            hash[switch] = true
         else
            hash[switch] = nextval
            ARGV.delete_at(index+1)
         end
      end
   }

   # Set synonymous switches to the same value, e.g. if -t is a synonym
   # for --test, and the user passes "--test", then set "-t" to the same
   # value that "--test" was set to.
   #
   # This allows users to refer to the long or short switch and get
   # the same value
   hash.dup.each{ |switch, val|
      if syns.keys.include?(switch)
         syns[switch] = [syns[switch]]
         syns[switch].each{ |key|
            hash[key] = val
         }
      end
   }

   # Get rid of leading "--" and "-" to make it easier to reference
   hash.dup.each{ |key, value|
      if key =~ /^-/
         if key[0,2] == '--'
            nkey = key.sub('--', '')
         else
            nkey = key.sub('-', '')
         end
         hash.delete(key)
         hash[nkey] = value
      end
   }

   hash
end