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