| Class | CommandLine::OptionParser |
| In: |
lib/commandline/optionparser/optionparser.rb
|
| Parent: | Object |
| DEFAULT_CONSOLE_WIDTH | = | 70 | ||
| MIN_CONSOLE_WIDTH | = | 10 | ||
| DEFAULT_BODY_INDENT | = | 4 | ||
| OPT_NOT_FOUND_BUT_REQUIRED | = | lambda { |opt| raise(MissingRequiredOptionError, "Missing required parameter '#{opt.names[0]}'.") | These helper lambdas are here because OptionParser is the object that calls them and hence knows the parameter order. | |
| GET_ARG_ARRAY | = | lambda { |opt, user_opt, _args| _args } | ||
| GET_ARGS | = | lambda { |opt, user_opt, _args| return true if _args.empty? |
| body_indent | [RW] | |
| columns | [RW] | |
| options | [R] | |
| posix | [R] | |
| tag_paragraph | [RW] | |
| unknown_options_action | [R] |
# File lib/commandline/optionparser/optionparser.rb, line 59
59: def initialize(*opts_and_props)
60: @posix = false
61: @unknown_options_action = :raise
62: @unknown_options = []
63: @opt_lookup_by_any_name = {}
64: @command_options = nil
65:
66: #
67: # Formatting defaults
68: #
69: console_width = ENV["COLUMNS"]
70: @columns =
71: if console_width.nil?
72: DEFAULT_CONSOLE_WIDTH
73: elsif console_width < MIN_CONSOLE_WIDTH
74: console_width
75: else
76: console_width - DEFAULT_BODY_INDENT
77: end
78: @body_indent = DEFAULT_BODY_INDENT
79: @tag_paragraph = false
80: @order = :index # | :alpha
81:
82: props = []
83: keys = {}
84: opts_and_props.flatten!
85: opts_and_props.delete_if { |op|
86: if Symbol === op
87: props << op; true
88: elsif Hash === op
89: keys.update(op); true
90: else
91: false
92: end
93: }
94:
95: props.each { |p|
96: case p
97: when :posix then @posix = true
98: else
99: raise(UnknownPropertyError, "Unknown property '#{p.inspect}'.")
100: end
101: }
102:
103: keys.each { |k,v|
104: case k
105: when :unknown_options_action
106: if [:collect, :ignore, :raise].include?(v)
107: @unknown_options_action = v
108: else
109: raise(UnknownPropertyError, "Unknown value '#{v}' for "+
110: ":unknown_options property.")
111: end
112: when :command_options
113: @command_options = v
114: @commands = v.keys
115: else
116: raise(UnknownPropertyError, "Unknown property '#{k.inspect}'.")
117: end
118: }
119: # :unknown_options => :collect
120: # :unknown_options => :ignore
121: # :unknown_options => :raise
122:
123: opts = opts_and_props
124:
125: @options = []
126: opts.each { |opt|
127: # If user wants to parse posix, then ensure all options are posix
128: raise(PosixMismatchError,
129: "Posix types do not match. #{opt.inspect}") if @posix && !opt.posix
130: @options << opt
131: }
132:
133: add_names(@options)
134:
135: yield self if block_given?
136: end
Add an option
# File lib/commandline/optionparser/optionparser.rb, line 160
160: def <<(option)
161: @options << option
162: add_names(option)
163: self
164: end
# File lib/commandline/optionparser/optionparser.rb, line 172
172: def add_names(*options)
173: options.flatten.each { |option|
174: raise "Wrong data type '#{option.name}." unless Option === option
175: option.names.each { |name|
176: raise(DuplicateOptionNameError,
177: "Duplicate option name '#{name}'.") if
178: @opt_lookup_by_any_name.has_key?(name)
179: @opt_lookup_by_any_name[name] = option
180: }
181: }
182: end
# File lib/commandline/optionparser/optionparser.rb, line 166
166: def add_option(*h)
167: opt = Option.new(*h)
168: @options << opt
169: add_names(opt)
170: end
# File lib/commandline/optionparser/optionparser.rb, line 275
275: def get_opt_args(opt, user_option, _args)
276: min, max = *opt.arity
277: size = _args.size
278:
279: if (min == max && max > 0 && size < max) || (size < min)
280: raise(MissingRequiredOptionArgumentError,
281: "Insufficient arguments #{_args.inspect}for option '#{user_option}' "+
282: "with :arity #{opt.arity.inspect}")
283: end
284:
285: if 0 == min && 0 == max
286: []
287: else
288: max = size if -1 == max
289: _args.slice!(0..[min, [max, size].min].max - 1)
290: end
291: end
# File lib/commandline/optionparser/optionparser.rb, line 293
293: def get_posix_re
294: flags = []
295: nflags = []
296: @options.each { |o|
297: if [0,0] == o.arity
298: flags << o.names[0][1..1]
299: else
300: nflags << o.names[0][1..1]
301: end
302: }
303: flags = flags.join
304: flags = flags.empty? ? "" : "[#{flags}\]+"
305: nflags = nflags.join
306: nflags = nflags.empty? ? "" : "[#{nflags}\]"
307: Regexp.new("^-(#{flags})(#{nflags})(.*)\$")
308: end
Parse the command line
# File lib/commandline/optionparser/optionparser.rb, line 202
202: def parse(argv=ARGV)
203: argv = [argv] unless Array === argv
204:
205: #
206: # Holds the results of each option. The key used is
207: # the first in the :names Array.
208: #
209: opts = Hash.new( :not_found )
210:
211: #
212: # A command is the first non-option free argument on the command line.
213: # This is a user selection and is the first argument in args.
214: # cmd = args.shift
215: # Example:
216: # cvs -v cmd --cmd-option arg
217: #
218: cmd = nil
219: cmd_options = {}
220:
221: #
222: # #parse_argv yields an array containing the option and its arguments.
223: # [opts, array_args]
224: # How do we collect all the arguments when OptionParser deal with an
225: # empty option list
226: #
227: parse_argv(argv) { |optarg|
228: user_option = optarg[0]
229: _args = optarg[1]
230:
231: m = nil
232: if @opt_lookup_by_any_name.has_key?(user_option) ||
233: 1 == (m = @opt_lookup_by_any_name.keys.grep(/^#{user_option}/)).size
234: user_option = m[0] if m
235: opt = @opt_lookup_by_any_name[user_option]
236: opt_key = opt.names[0]
237:
238: opt_args = get_opt_args(opt, user_option, _args)
239: opts[opt_key] =
240: if Proc === opt.opt_found
241: # Take the arguments depending upon arity
242: opt.opt_found.call(opt, user_option, opt_args)
243: else
244: opt.opt_found
245: end
246: # Collect any remaining args
247: @args += _args
248: elsif :collect == @unknown_options_action
249: @unknown_options << user_option
250: elsif :ignore == @unknown_options_action
251: else
252: raise(UnknownOptionError, "Unknown option '#{user_option}'"+
253: "#{$DEBUG ? ' in ' + @opt_lookup_by_any_name.keys.inspect : ''}.")
254: end
255: }
256:
257: #
258: # Call :not_found for all the options not on the command line.
259: #
260: @options.each { |opt|
261: name = opt.names[0]
262: if :not_found == opts[name]
263: opts[name] =
264: if Proc === opt.opt_not_found
265: opt.opt_not_found.call(opt)
266: else
267: opt.opt_not_found
268: end
269: end
270: }
271:
272: OptionData.new(argv, opts, @unknown_options, @args, @not_parsed, cmd)
273: end
Seperates options from arguments Does not look for valid options ( or should it? )
%w(-fred file1 file2) => ["-fred", ["file1", "file2"]] %w(--fred -t -h xyz) => ["--fred", []] ["-t", []] ["-h", ["xyz"]] %w(-f=file) => ["-f", ["file"]] %w(--file=fred) => ["--file", ["fred"]] %w(-file=fred) => ["-file", ["fred"]] ['-file="fred1 fred2"'] => ["-file", ["fred1", "fred2"]]
# File lib/commandline/optionparser/optionparser.rb, line 391
391: def parse_argv(argv, &block)
392: return parse_posix_argv(argv, &block) if @posix
393:
394: @not_parsed = []
395: tagged = []
396: argv.each_with_index { |e,i|
397: if "--" == e
398: @not_parsed = argv[(i+1)..(argv.size+1)]
399: break
400: elsif "-" == e
401: tagged << [:arg, e]
402: elsif ?- == e[0]
403: m = Option::GENERAL_OPT_EQ_ARG_RE.match(e)
404: if m.nil?
405: tagged << [:opt, e]
406: else
407: tagged << [:opt, m[1]]
408: tagged << [:arg, m[2]]
409: end
410: else
411: tagged << [:arg, e]
412: end
413: }
414:
415: #
416: # The tagged array has the form:
417: # [
418: # [:opt, "-a"], [:arg, "filea"],
419: # [:opt, "-b"], [:arg, "fileb"],
420: # #[:not_parsed, ["-z", "-y", "file", "file2", "-a", "-b"]]
421: # ]
422:
423: #
424: # Now, combine any adjacent args such that
425: # [[:arg, "arg1"], [:arg, "arg2"]]
426: # becomes
427: # [[:args, ["arg1", "arg2"]]]
428: # and the final result should be
429: # [ "--file", ["arg1", "arg2"]]
430: #
431:
432: parsed = []
433: @args = []
434: tagged.each { |e|
435: if :opt == e[0]
436: parsed << [e[1], []]
437: elsif :arg == e[0]
438: if Array === parsed[-1]
439: parsed[-1][-1] += [e[1]]
440: else
441: @args << e[1]
442: end
443: else
444: raise "How did we get here?"
445: end
446: }
447: parsed.each { |e| block.call(e) }
448: end
# File lib/commandline/optionparser/optionparser.rb, line 311
311: def parse_posix_argv(argv)
312: re = @posix ? get_posix_re : Option::GENERAL_OPT_EQ_ARG_RE
313: p re if $DEBUG
314: tagged = []
315:
316: #
317: # A Posix command line must have all the options precede
318: # non option arguments. For example
319: # :names => -h -e -l -p -s
320: # where -p can take an argument
321: # Command line can read:
322: # -helps => -h -e -l -p s
323: # -p fred non-opt-arg
324: # -p fred non-opt-arg -h # not ok
325: # -he -popt-arg1 -popt-arg2 non-opt-arg
326: # -p=fred # this is not legal?
327: # -pfred === -p fred
328: #
329:
330: #"-helps" "-pfred" "-p" "fred"
331: #-h -e -l -p [s] -p [fred] -p [fred]
332: #[-h, []], [-e []], [-l, []], [-p, [s]], -p
333:
334: argv.each { |e|
335: m = re.match(e)
336: if m.nil?
337: tagged << [:arg, e]
338: else
339: raise "houston, we have a problem" if m.nil?
340: unless m[1].empty?
341: m[1].split(//).each { |e| tagged << [:opt, "-#{e}"] }
342: end
343:
344: unless m[2].empty?
345: tagged << [:opt, "-#{m[2]}"]
346: tagged << [:arg, m[3]] unless m[3].empty?
347: end
348: end
349: }
350:
351: if $DEBUG
352: print "Tagged:"
353: p tagged
354: end
355: #
356: # Now, combine any adjacent args such that
357: # [[:arg, "arg1"], [:arg, "arg2"]]
358: # becomes
359: # [[:args, ["arg1", "arg2"]]]
360: # and the final result should be
361: # [ "--file", ["arg1", "arg2"]]
362: #
363:
364: parsed = []
365: @args = []
366: tagged.each { |e|
367: if :opt == e[0]
368: parsed << [e[1], []]
369: else
370: if Array === parsed[-1]
371: parsed[-1][-1] += [e[1]]
372: else
373: @args << e[1]
374: end
375: end
376: }
377: parsed.each { |e| yield e }
378: end
# File lib/commandline/optionparser/optionparser.rb, line 454
454: def to_s(sep="\n")
455: return "" if @options.empty?
456:
457: require 'text/format'
458: @f = Text::Format.new
459: @f.columns = @columns
460: @f.first_indent = 4
461: @f.body_indent = 8
462: @f.tag_paragraph = false
463:
464: header = ["OPTIONS\n"]
465: s = []
466: @options.each { |opt|
467: opt_str = []
468: if block_given?
469: result = yield(opt.names, opt.opt_description, opt.arg_description)
470: if result.kind_of?(String)
471: opt_str << result unless result.empty?
472: elsif result.nil?
473: opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
474: elsif result.kind_of?(Array) && 3 == result.size
475: opt_str << format_option(*result)
476: else
477: raise "Invalid return value #{result.inspect} from yield block "+
478: "attached to #to_s."
479: end
480: else
481: opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
482: end
483: s << opt_str.join unless opt_str.empty?
484: }
485: #s.collect! { |i| i.kind_of?(Array) && /\n+/ =~ i[0] ? i.join : f.paragraphs(i) }
486: [header, s].flatten.join(sep)
487: end
# File lib/commandline/optionparser/optionparser.rb, line 184
184: def validate_parse_options(h)
185: h[:names].each { |name| check_option_name(name) }
186:
187: #if @posix
188: # all are single-dash:single-char OR double-dash:multi-char
189: #else if unix compliant
190: # single-dash only
191: #else any - does not support combination - try to on single/single
192: #end
193: end
# File lib/commandline/optionparser/optionparser.rb, line 489
489: def format_option(names, opt_desc, arg_desc)
490: # TODO: Clean up the magic numbers
491:
492: f = Text::Format.new
493: f.columns = @columns
494: f.first_indent = 4
495: f.body_indent = 8
496: f.tabstop = 4
497: s = ""
498: s << f.format("#{names.join(",")} #{arg_desc}")
499: #if 7 == s.last.size
500: if 7 == s.size
501: f.first_indent = f.first_indent - 2
502: s.rstrip!
503: s << f.format(opt_desc)
504: #elsif 8 == s.last.size
505: elsif 8 == s.size
506: f.first_indent = f.first_indent - 3
507: s.rstrip!
508: s << f.format(opt_desc)
509: else
510: f.first_indent = 2 * f.first_indent
511: s << f.format(opt_desc)
512: end
513: end