OptionParser 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +110 -0
- data/docs/index.html +846 -0
- data/lib/commandline/optionparser.rb +16 -0
- data/lib/commandline/optionparser/option.rb +183 -0
- data/lib/commandline/optionparser/optiondata.rb +54 -0
- data/lib/commandline/optionparser/optionparser.rb +511 -0
- data/lib/commandline/text/format.rb +1451 -0
- data/test/tc_option.rb +121 -0
- data/test/testall.rb +16 -0
- metadata +50 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# $Id$
|
2
|
+
# $Source$
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# Loader
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 2005/06/14 Birthday
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'commandline/optionparser/option'
|
15
|
+
require 'commandline/optionparser/optionparser'
|
16
|
+
require 'commandline/optionparser/optiondata'
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# $Id$
|
2
|
+
# $Source$
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005 Jim Freeze
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# A very flexible commandline parser
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 04/01/2005 Birthday
|
12
|
+
#
|
13
|
+
|
14
|
+
module CommandLine
|
15
|
+
|
16
|
+
class Option
|
17
|
+
class OptionError < StandardError; end
|
18
|
+
class InvalidOptionNameError < OptionError; end
|
19
|
+
class InvalidArgumentError < OptionError; end
|
20
|
+
class MissingOptionNameError < OptionError; end
|
21
|
+
class InvalidArgumentArityError < OptionError; end
|
22
|
+
class MissingPropertyError < OptionError; end
|
23
|
+
class InvalidPropertyError < OptionError; end
|
24
|
+
class InvalidConstructionError < OptionError; end
|
25
|
+
|
26
|
+
attr_accessor :posix
|
27
|
+
|
28
|
+
#
|
29
|
+
GENERAL_OPT_EQ_ARG_RE = /^(-{1,2}[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/ # :nodoc:
|
30
|
+
GNU_OPT_EQ_ARG_RE = /^(--[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/
|
31
|
+
#OPTION_RE = /^-{1,2}([a-zA-Z]+\w*)(.*)/
|
32
|
+
#UNIX_OPT_EQ_ARG_RE = /^(-[a-zA-Z])=(.*)$/
|
33
|
+
#UNIX_OPT_EQorSP_ARG_RE = /^(-[a-zA-Z])(=|\s+)(.*)$/
|
34
|
+
|
35
|
+
POSIX_OPTION_RE = /^-[a-zA-Z]?$/
|
36
|
+
# need to change this to support - and --
|
37
|
+
NON_POSIX_OPTION_RE = /^(-|-{1,2}[a-zA-Z_]+[-_a-zA-Z0-9]*)/
|
38
|
+
|
39
|
+
PROPERTIES = [ :arg_arity, :opt_description, :arg_description,
|
40
|
+
:opt_found, :opt_not_found, :posix
|
41
|
+
]
|
42
|
+
|
43
|
+
FLAG_BASE_OPTS = {
|
44
|
+
:arg_arity => [0,0],
|
45
|
+
# :opt_description => nil,
|
46
|
+
:arg_description => "",
|
47
|
+
:opt_found => true,
|
48
|
+
:opt_not_found => false
|
49
|
+
}
|
50
|
+
|
51
|
+
# You get these without asking for them
|
52
|
+
DEFAULT_OPTS = {
|
53
|
+
:arg_arity => [1,1],
|
54
|
+
:opt_description => "",
|
55
|
+
:arg_description => "",
|
56
|
+
:opt_found => true,
|
57
|
+
:opt_not_found => false
|
58
|
+
}
|
59
|
+
|
60
|
+
#
|
61
|
+
# Option.new(:posix => true, :names => %w(--opt), :flag)
|
62
|
+
#
|
63
|
+
# TODO: Should we test and raise key is not one of :names, opt_description, ...
|
64
|
+
# This will prevent typos. Can users add properties to an Option that are their own?
|
65
|
+
def initialize(*all)
|
66
|
+
@posix = false
|
67
|
+
|
68
|
+
raise(MissingPropertyError,
|
69
|
+
"No properties specified for new #{self.class}.") if all.empty?
|
70
|
+
|
71
|
+
until Hash === all[0]
|
72
|
+
case (prop = all.shift)
|
73
|
+
when :flag then @flag = true
|
74
|
+
when :posix then @posix = true
|
75
|
+
else
|
76
|
+
raise(InvalidPropertyError, "Unknown option setting '#{prop}'.")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
properties = all[0]
|
81
|
+
|
82
|
+
type = @flag.nil? ? :default : :flag
|
83
|
+
merge_hash =
|
84
|
+
case type
|
85
|
+
when :flag then FLAG_BASE_OPTS
|
86
|
+
when :default then DEFAULT_OPTS
|
87
|
+
else raise(InvalidConstructionError,
|
88
|
+
"Invalid arguments to Option.new. Must be a property hash with "+
|
89
|
+
"keys [:names, :arg_arity, :opt_description, :arg_description, "+
|
90
|
+
":opt_found, :opt_not_found] or "+
|
91
|
+
"an option type [:flag, :default].")
|
92
|
+
end
|
93
|
+
|
94
|
+
raise(InvalidPropertyError,
|
95
|
+
"Don't understand argument of type '#{properties.class}' => "+
|
96
|
+
"#{properties.inspect} passed to #{self.class}.new. Looking "+
|
97
|
+
"for type Hash.") unless properties.kind_of?(Hash)
|
98
|
+
|
99
|
+
if properties.has_key?(:posix)
|
100
|
+
@posix = properties[:posix]
|
101
|
+
properties.delete(:posix)
|
102
|
+
end
|
103
|
+
@properties = merge_hash.merge(properties)
|
104
|
+
|
105
|
+
@properties[:names] = [@properties[:names]] unless
|
106
|
+
@properties[:names].kind_of?(Array)
|
107
|
+
|
108
|
+
arg_arity = @properties[:arg_arity]
|
109
|
+
@properties[:arg_arity] = [arg_arity, arg_arity] unless
|
110
|
+
arg_arity.kind_of?(Array)
|
111
|
+
|
112
|
+
raise "Invalid value for arg_arity '#{arg_arity}'." unless
|
113
|
+
arg_arity.kind_of?(Array) || arg_arity.kind_of?(Fixnum)
|
114
|
+
|
115
|
+
raise(InvalidArgumentArityError,
|
116
|
+
"Conflicting value given to new option: :flag "+
|
117
|
+
"and :arg_arity = #{properties[:arg_arity].inspect}.") if
|
118
|
+
:flag == type && [0,0] != @properties[:arg_arity]
|
119
|
+
|
120
|
+
names = @properties[:names]
|
121
|
+
raise(MissingOptionNameError,
|
122
|
+
"Attempt to create an Option without :names defined.") if
|
123
|
+
names.nil? || names.empty?
|
124
|
+
|
125
|
+
names.each { |name| check_option_name(name) }
|
126
|
+
validate_arity @properties[:arg_arity]
|
127
|
+
|
128
|
+
create_opt_description if :flag == type
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_opt_description
|
132
|
+
return if @properties.has_key?(:opt_description)
|
133
|
+
word = @properties[:names].grep(/^--\w.+/)
|
134
|
+
if word.empty?
|
135
|
+
@properties[:opt_description] = ""
|
136
|
+
else
|
137
|
+
@properties[:opt_description] = "Sets #{word.first[2..-1]} to true."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_option_name(name)
|
142
|
+
raise(InvalidOptionNameError,
|
143
|
+
"Option name '#{name}' contains invalid space.") if /\s+/.match(name)
|
144
|
+
|
145
|
+
if @posix
|
146
|
+
raise(InvalidOptionNameError,
|
147
|
+
"Option name '#{name}' is invalid.") unless POSIX_OPTION_RE.match(name)
|
148
|
+
else
|
149
|
+
raise(InvalidOptionNameError,
|
150
|
+
"Option name '#{name}' is invalid.") unless NON_POSIX_OPTION_RE.match(name)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def validate_arity(arity)
|
155
|
+
raise ":arg_arity is nil" if arity.nil?
|
156
|
+
min, max = *arity
|
157
|
+
|
158
|
+
raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
|
159
|
+
"greater than or equal to 0.") unless min >= 0
|
160
|
+
raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
|
161
|
+
"greater than or equal to -1.") if max < -1
|
162
|
+
raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
|
163
|
+
"greater than minimum arg_arity '#{min}'.") if max < min && max != -1
|
164
|
+
if @posix
|
165
|
+
raise(InvalidArgumentArityError, "Posix options only support :arg_arity "+
|
166
|
+
"of [0,0] or [1,1].") unless ([0,0] == arity) || ([1,1] == arity)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def method_missing(sym, *args)
|
171
|
+
raise "Unknown property '#{sym}' for option
|
172
|
+
#{@properties[:names].inspect unless @properties[:names].nil?}." unless
|
173
|
+
@properties.has_key?(sym) || PROPERTIES.include?(sym)
|
174
|
+
@properties[sym, *args]
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_hash
|
178
|
+
Marshal.load(Marshal.dump(@properties))
|
179
|
+
end
|
180
|
+
|
181
|
+
end#class Option
|
182
|
+
|
183
|
+
end#module CommandLine
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# $Id$
|
2
|
+
# $Source$
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005 Jim Freeze
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# A very flexible commandline parser
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 04/01/2005 Birthday
|
12
|
+
#
|
13
|
+
#
|
14
|
+
|
15
|
+
module CommandLine
|
16
|
+
|
17
|
+
#
|
18
|
+
# Data resulting from parsing a command line (Array)
|
19
|
+
# using a particular OptionParser object
|
20
|
+
#
|
21
|
+
class OptionData
|
22
|
+
attr_reader :argv, :unknown_options, :args, :not_parsed, :cmd
|
23
|
+
|
24
|
+
class OptionDataError < StandardError; end
|
25
|
+
class UnknownOptionError < OptionDataError; end
|
26
|
+
|
27
|
+
def initialize(argv, opts, unknown_options, args, not_parsed, cmd)
|
28
|
+
@opts = {}
|
29
|
+
opts.each { |k,v|
|
30
|
+
@opts[k] =
|
31
|
+
begin
|
32
|
+
Marshal.load(Marshal.dump(v))
|
33
|
+
rescue
|
34
|
+
v
|
35
|
+
end
|
36
|
+
}
|
37
|
+
@unknown_options = Marshal.load(Marshal.dump(unknown_options))
|
38
|
+
@not_parsed = Marshal.load(Marshal.dump(not_parsed))
|
39
|
+
@argv = Marshal.load(Marshal.dump(argv))
|
40
|
+
@args = Marshal.load(Marshal.dump(args))
|
41
|
+
@cmd = Marshal.load(Marshal.dump(cmd))
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](key)
|
45
|
+
if @opts.has_key?(key)
|
46
|
+
@opts[key]
|
47
|
+
else
|
48
|
+
raise(UnknownOptionError, "Unknown option '#{key}'.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end#class OptionData
|
53
|
+
|
54
|
+
end#module CommandLine
|
@@ -0,0 +1,511 @@
|
|
1
|
+
# $Id$
|
2
|
+
# $Source$
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005 Jim Freeze
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# A very flexible commandline parser
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 04/01/2005 Birthday
|
12
|
+
#
|
13
|
+
# :include: README
|
14
|
+
#
|
15
|
+
|
16
|
+
module CommandLine
|
17
|
+
|
18
|
+
class OptionParser
|
19
|
+
attr_reader :posix, :unknown_options, :unknown_options_action
|
20
|
+
|
21
|
+
attr_accessor :columns, :body_indent, :tag_paragraph
|
22
|
+
|
23
|
+
DEFAULT_CONSOLE_WIDTH = 70
|
24
|
+
MIN_CONSOLE_WIDTH = 10
|
25
|
+
DEFAULT_BODY_INDENT = 4
|
26
|
+
|
27
|
+
#
|
28
|
+
# These helper lambdas are here because OptionParser is the object
|
29
|
+
# that calls them and hence knows the parameter order.
|
30
|
+
#
|
31
|
+
|
32
|
+
OPT_NOT_FOUND_BUT_REQUIRED = lambda { |opt|
|
33
|
+
raise(MissingRequiredOptionError,
|
34
|
+
"Missing required parameter '#{opt.names[0]}'.")
|
35
|
+
}
|
36
|
+
|
37
|
+
GET_ARG_ARRAY = lambda { |opt, user_opt, args| args }
|
38
|
+
|
39
|
+
GET_ARGS = lambda { |opt, user_opt, args|
|
40
|
+
return true if args.empty?
|
41
|
+
return args[0] if 1 == args.size
|
42
|
+
args
|
43
|
+
}
|
44
|
+
|
45
|
+
#
|
46
|
+
# Option Errors. Not the oxymoron below for MissingRequiredOptionError.
|
47
|
+
# The user can make an option required by adding the OPT_NOT_FOUND_BUT_REQUIRED
|
48
|
+
# lambda for the #opt_not_found action.
|
49
|
+
#
|
50
|
+
class OptionParserError < StandardError; end
|
51
|
+
class DuplicateOptionNameError < OptionParserError; end
|
52
|
+
class MissingRequiredOptionError < OptionParserError; end
|
53
|
+
class MissingRequiredOptionArgumentError < OptionParserError; end
|
54
|
+
class UnknownOptionError < OptionParserError; end
|
55
|
+
class UnknownPropertyError < OptionParserError; end
|
56
|
+
class PosixMismatchError < OptionParserError; end
|
57
|
+
|
58
|
+
def initialize(*opts_and_props)
|
59
|
+
@posix = false
|
60
|
+
@unknown_options_action = :raise
|
61
|
+
@unknown_options = []
|
62
|
+
@opt_lookup_by_any_name = {}
|
63
|
+
@command_options = nil
|
64
|
+
|
65
|
+
#
|
66
|
+
# Formatting defaults
|
67
|
+
#
|
68
|
+
console_width = ENV["COLUMNS"]
|
69
|
+
@columns =
|
70
|
+
if console_width.nil?
|
71
|
+
DEFAULT_CONSOLE_WIDTH
|
72
|
+
elsif console_width < MIN_CONSOLE_WIDTH
|
73
|
+
console_width
|
74
|
+
else
|
75
|
+
console_width - DEFAULT_BODY_INDENT
|
76
|
+
end
|
77
|
+
@body_indent = DEFAULT_BODY_INDENT
|
78
|
+
@tag_paragraph = false
|
79
|
+
@order = :index # | :alpha
|
80
|
+
|
81
|
+
props = []
|
82
|
+
keys = {}
|
83
|
+
opts_and_props.flatten!
|
84
|
+
opts_and_props.delete_if { |op|
|
85
|
+
if Symbol === op
|
86
|
+
props << op; true
|
87
|
+
elsif Hash === op
|
88
|
+
keys.update(op); true
|
89
|
+
else
|
90
|
+
false
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
props.each { |p|
|
95
|
+
case p
|
96
|
+
when :posix then @posix = true
|
97
|
+
else
|
98
|
+
raise(UnknownPropertyError, "Unknown property '#{p.inspect}'.")
|
99
|
+
end
|
100
|
+
}
|
101
|
+
|
102
|
+
keys.each { |k,v|
|
103
|
+
case k
|
104
|
+
when :unknown_options_action
|
105
|
+
if [:collect, :ignore, :raise].include?(v)
|
106
|
+
@unknown_options_action = v
|
107
|
+
else
|
108
|
+
raise(UnknownPropertyError, "Unknown value '#{v}' for "+
|
109
|
+
":unknown_options property.")
|
110
|
+
end
|
111
|
+
when :command_options
|
112
|
+
@command_options = v
|
113
|
+
@commands = v.keys
|
114
|
+
else
|
115
|
+
raise(UnknownPropertyError, "Unknown property '#{k.inspect}'.")
|
116
|
+
end
|
117
|
+
}
|
118
|
+
# :unknown_options => :collect
|
119
|
+
# :unknown_options => :ignore
|
120
|
+
# :unknown_options => :raise
|
121
|
+
|
122
|
+
opts = opts_and_props
|
123
|
+
|
124
|
+
@options = []
|
125
|
+
opts.each { |opt|
|
126
|
+
# If user wants to parse posix, then ensure all options are posix
|
127
|
+
raise(PosixMismatchError,
|
128
|
+
"Posix types do not match. #{opt.inspect}") if @posix && !opt.posix
|
129
|
+
@options << opt
|
130
|
+
}
|
131
|
+
|
132
|
+
#p "options-"*5
|
133
|
+
#p @options
|
134
|
+
add_names(@options)
|
135
|
+
|
136
|
+
yield self if block_given?
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# add_option :names => %w{--file --use-this-file -f},
|
141
|
+
# :
|
142
|
+
#
|
143
|
+
# add_option :names => %w(--version -v),
|
144
|
+
# :arg_arity => [0,0], # default
|
145
|
+
# :option_description => "Returns Version"
|
146
|
+
# add_option :names => %w(--file -f),
|
147
|
+
# :arg_arity => [1,:unlimited],
|
148
|
+
# :opt_description => "Define the output filename.",
|
149
|
+
# :arg_description => "Output file"
|
150
|
+
# :opt_exists => lambda {}
|
151
|
+
# :opt_not_exists => lambda {}
|
152
|
+
# :option_found
|
153
|
+
# :no_option_found
|
154
|
+
# :opt_found => lambda {}
|
155
|
+
# :no_opt_found => lambda {}
|
156
|
+
#
|
157
|
+
|
158
|
+
#
|
159
|
+
# Add an option
|
160
|
+
#
|
161
|
+
def <<(option)
|
162
|
+
@options << option
|
163
|
+
add_names(option)
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def add_names(*options)
|
168
|
+
options.flatten.each { |option|
|
169
|
+
option.names.each { |name|
|
170
|
+
raise(DuplicateOptionNameError,
|
171
|
+
"Duplicate option name '#{name}'.") if
|
172
|
+
@opt_lookup_by_any_name.has_key?(name)
|
173
|
+
@opt_lookup_by_any_name[name] = option
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def validate_parse_options(h)
|
179
|
+
h[:names].each { |name| check_option_name(name) }
|
180
|
+
|
181
|
+
#if @posix
|
182
|
+
# all are single-dash:single-char OR double-dash:multi-char
|
183
|
+
#else if unix compliant
|
184
|
+
# single-dash only
|
185
|
+
#else any - does not support combination - try to on single/single
|
186
|
+
#end
|
187
|
+
end
|
188
|
+
|
189
|
+
# def [](opt)
|
190
|
+
# @options[@opt_lookup_by_any_name[opt][0]]
|
191
|
+
# end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Parse the command line
|
195
|
+
#
|
196
|
+
def parse(argv=ARGV)
|
197
|
+
argv = [argv] unless Array === argv
|
198
|
+
|
199
|
+
#
|
200
|
+
# Holds the results of each option. The key used is
|
201
|
+
# the first in the :names Array.
|
202
|
+
#
|
203
|
+
opts = Hash.new( :not_found )
|
204
|
+
|
205
|
+
#
|
206
|
+
# A command is the first non-option free argument on the command line.
|
207
|
+
# This is a user selection and is the first argument in args.
|
208
|
+
# cmd = args.shift
|
209
|
+
# Example:
|
210
|
+
# cvs -v cmd --cmd-option arg
|
211
|
+
#
|
212
|
+
cmd = nil
|
213
|
+
cmd_options = {}
|
214
|
+
|
215
|
+
#
|
216
|
+
# #parse_argv yields an array containing the option and its arguments.
|
217
|
+
# [opts, array_args]
|
218
|
+
# How do we collect all the arguments when OptionParser deal with an
|
219
|
+
# empty option list
|
220
|
+
#
|
221
|
+
parse_argv(argv) { |optarg|
|
222
|
+
user_option = optarg[0]
|
223
|
+
args = optarg[1]
|
224
|
+
|
225
|
+
m = nil
|
226
|
+
if @opt_lookup_by_any_name.has_key?(user_option) ||
|
227
|
+
1 == (m = @opt_lookup_by_any_name.keys.grep(/^#{user_option}/)).size
|
228
|
+
user_option = m[0] if m
|
229
|
+
opt = @opt_lookup_by_any_name[user_option]
|
230
|
+
opt_key = opt.names[0]
|
231
|
+
|
232
|
+
opts[opt_key] =
|
233
|
+
if Proc === opt.opt_found
|
234
|
+
# Take the arguments depending upon arity
|
235
|
+
opt_args = get_opt_args(opt, user_option, args)
|
236
|
+
opt.opt_found.call(opt, user_option, opt_args)
|
237
|
+
else
|
238
|
+
opt.opt_found
|
239
|
+
end
|
240
|
+
# Collect any remaining args
|
241
|
+
@args += args
|
242
|
+
|
243
|
+
elsif :collect == @unknown_options_action
|
244
|
+
@unknown_options << user_option
|
245
|
+
elsif :ignore == @unknown_options_action
|
246
|
+
else
|
247
|
+
raise(UnknownOptionError, "Unknown option '#{user_option}' in "+
|
248
|
+
"#{@opt_lookup_by_any_name.inspect}.")
|
249
|
+
end
|
250
|
+
}
|
251
|
+
|
252
|
+
#
|
253
|
+
# Call :not_found for all the options not on the command line.
|
254
|
+
#
|
255
|
+
@options.each { |opt|
|
256
|
+
name = opt.names[0]
|
257
|
+
if :not_found == opts[name]
|
258
|
+
opts[name] =
|
259
|
+
if Proc === opt.opt_not_found
|
260
|
+
opt.opt_not_found.call(opt)
|
261
|
+
else
|
262
|
+
opt.opt_not_found
|
263
|
+
end
|
264
|
+
end
|
265
|
+
}
|
266
|
+
|
267
|
+
OptionData.new(argv, opts, @unknown_options, @args, @not_parsed, cmd)
|
268
|
+
end
|
269
|
+
|
270
|
+
def get_opt_args(opt, user_option, args)
|
271
|
+
min, max = *opt.arg_arity
|
272
|
+
size = args.size
|
273
|
+
|
274
|
+
if (min == max && max > 0 && size < max) || (size < min)
|
275
|
+
raise(MissingRequiredOptionArgumentError,
|
276
|
+
"Insufficient arguments #{args.inspect}for option '#{user_option}' "+
|
277
|
+
"with :arg_arity #{opt.arg_arity.inspect}")
|
278
|
+
end
|
279
|
+
|
280
|
+
if 0 == min && 0 == max
|
281
|
+
[]
|
282
|
+
else
|
283
|
+
max = size if -1 == max
|
284
|
+
args.slice!(0..[min, [max, size].min].max - 1)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_posix_re
|
289
|
+
flags = []
|
290
|
+
nflags = []
|
291
|
+
@options.each { |o|
|
292
|
+
if [0,0] == o.arg_arity
|
293
|
+
flags << o.names[0][1..1]
|
294
|
+
else
|
295
|
+
nflags << o.names[0][1..1]
|
296
|
+
end
|
297
|
+
}
|
298
|
+
flags = flags.join
|
299
|
+
flags = flags.empty? ? "" : "[#{flags}\]+"
|
300
|
+
nflags = nflags.join
|
301
|
+
nflags = nflags.empty? ? "" : "[#{nflags}\]"
|
302
|
+
Regexp.new("^-(#{flags})(#{nflags})(.*)\$")
|
303
|
+
end
|
304
|
+
|
305
|
+
#######################################################################
|
306
|
+
def parse_posix_argv(argv)
|
307
|
+
re = @posix ? get_posix_re : Option::GENERAL_OPT_EQ_ARG_RE
|
308
|
+
p re if $DEBUG
|
309
|
+
tagged = []
|
310
|
+
|
311
|
+
#
|
312
|
+
# A Posix command line must have all the options precede
|
313
|
+
# non option arguments. For example
|
314
|
+
# :names => -h -e -l -p -s
|
315
|
+
# where -p can take an argument
|
316
|
+
# Command line can read:
|
317
|
+
# -helps => -h -e -l -p s
|
318
|
+
# -p fred non-opt-arg
|
319
|
+
# -p fred non-opt-arg -h # not ok
|
320
|
+
# -he -popt-arg1 -popt-arg2 non-opt-arg
|
321
|
+
# -p=fred # this is not legal?
|
322
|
+
# -pfred === -p fred
|
323
|
+
#
|
324
|
+
|
325
|
+
#"-helps" "-pfred" "-p" "fred"
|
326
|
+
#-h -e -l -p [s] -p [fred] -p [fred]
|
327
|
+
#[-h, []], [-e []], [-l, []], [-p, [s]], -p
|
328
|
+
|
329
|
+
argv.each { |e|
|
330
|
+
m = re.match(e)
|
331
|
+
if m.nil?
|
332
|
+
tagged << [:arg, e]
|
333
|
+
else
|
334
|
+
raise "houston, we have a problem" if m.nil?
|
335
|
+
unless m[1].empty?
|
336
|
+
m[1].split(//).each { |e| tagged << [:opt, "-#{e}"] }
|
337
|
+
end
|
338
|
+
|
339
|
+
unless m[2].empty?
|
340
|
+
tagged << [:opt, "-#{m[2]}"]
|
341
|
+
tagged << [:arg, m[3]] unless m[3].empty?
|
342
|
+
end
|
343
|
+
end
|
344
|
+
}
|
345
|
+
|
346
|
+
if $DEBUG
|
347
|
+
print "Tagged:"
|
348
|
+
p tagged
|
349
|
+
end
|
350
|
+
#
|
351
|
+
# Now, combine any adjacent args such that
|
352
|
+
# [[:arg, "arg1"], [:arg, "arg2"]]
|
353
|
+
# becomes
|
354
|
+
# [[:args, ["arg1", "arg2"]]]
|
355
|
+
# and the final result should be
|
356
|
+
# [ "--file", ["arg1", "arg2"]]
|
357
|
+
#
|
358
|
+
|
359
|
+
parsed = []
|
360
|
+
@args = []
|
361
|
+
tagged.each { |e|
|
362
|
+
if :opt == e[0]
|
363
|
+
parsed << [e[1], []]
|
364
|
+
else
|
365
|
+
if Array === parsed[-1]
|
366
|
+
parsed[-1][-1] += [e[1]]
|
367
|
+
else
|
368
|
+
@args << e[1]
|
369
|
+
end
|
370
|
+
end
|
371
|
+
}
|
372
|
+
parsed.each { |e| yield e }
|
373
|
+
end
|
374
|
+
|
375
|
+
#
|
376
|
+
# Seperates options from arguments
|
377
|
+
# Does not look for valid options ( or should it? )
|
378
|
+
#
|
379
|
+
# %w(-fred file1 file2) => ["-fred", ["file1", "file2"]]
|
380
|
+
# %w(--fred -t -h xyz) => ["--fred", []] ["-t", []] ["-h", ["xyz"]]
|
381
|
+
# %w(-f=file) => ["-f", ["file"]]
|
382
|
+
# %w(--file=fred) => ["--file", ["fred"]]
|
383
|
+
# %w(-file=fred) => ["-file", ["fred"]]
|
384
|
+
# ['-file="fred1 fred2"'] => ["-file", ["fred1", "fred2"]]
|
385
|
+
#
|
386
|
+
def parse_argv(argv, &block)
|
387
|
+
return parse_posix_argv(argv, &block) if @posix
|
388
|
+
|
389
|
+
@not_parsed = []
|
390
|
+
tagged = []
|
391
|
+
argv.each_with_index { |e,i|
|
392
|
+
if "--" == e
|
393
|
+
@not_parsed = argv[(i+1)..(argv.size+1)]
|
394
|
+
break
|
395
|
+
elsif "-" == e
|
396
|
+
tagged << [:arg, e]
|
397
|
+
elsif ?- == e[0]
|
398
|
+
m = Option::GENERAL_OPT_EQ_ARG_RE.match(e)
|
399
|
+
if m.nil?
|
400
|
+
tagged << [:opt, e]
|
401
|
+
else
|
402
|
+
tagged << [:opt, m[1]]
|
403
|
+
tagged << [:arg, m[2]]
|
404
|
+
end
|
405
|
+
else
|
406
|
+
tagged << [:arg, e]
|
407
|
+
end
|
408
|
+
}
|
409
|
+
|
410
|
+
#
|
411
|
+
# The tagged array has the form:
|
412
|
+
# [
|
413
|
+
# [:opt, "-a"], [:arg, "filea"],
|
414
|
+
# [:opt, "-b"], [:arg, "fileb"],
|
415
|
+
# #[:not_parsed, ["-z", "-y", "file", "file2", "-a", "-b"]]
|
416
|
+
# ]
|
417
|
+
|
418
|
+
#
|
419
|
+
# Now, combine any adjacent args such that
|
420
|
+
# [[:arg, "arg1"], [:arg, "arg2"]]
|
421
|
+
# becomes
|
422
|
+
# [[:args, ["arg1", "arg2"]]]
|
423
|
+
# and the final result should be
|
424
|
+
# [ "--file", ["arg1", "arg2"]]
|
425
|
+
#
|
426
|
+
|
427
|
+
parsed = []
|
428
|
+
@args = []
|
429
|
+
tagged.each { |e|
|
430
|
+
if :opt == e[0]
|
431
|
+
parsed << [e[1], []]
|
432
|
+
elsif :arg == e[0]
|
433
|
+
if Array === parsed[-1]
|
434
|
+
parsed[-1][-1] += [e[1]]
|
435
|
+
else
|
436
|
+
@args << e[1]
|
437
|
+
end
|
438
|
+
else
|
439
|
+
raise "How did we get here?"
|
440
|
+
end
|
441
|
+
}
|
442
|
+
parsed.each { |e| block.call(e) }
|
443
|
+
end
|
444
|
+
|
445
|
+
def to_str
|
446
|
+
to_s
|
447
|
+
end
|
448
|
+
|
449
|
+
def to_s(sep="\n")
|
450
|
+
require 'commandline/text/format'
|
451
|
+
@f = Text::Format.new
|
452
|
+
@f.columns = @columns
|
453
|
+
@f.first_indent = 4
|
454
|
+
@f.body_indent = 8
|
455
|
+
@f.tag_paragraph = false
|
456
|
+
|
457
|
+
header = ["OPTIONS\n"]
|
458
|
+
s = []
|
459
|
+
@options.each { |opt|
|
460
|
+
opt_str = []
|
461
|
+
if block_given?
|
462
|
+
result = yield(opt.names, opt.opt_description, opt.arg_description)
|
463
|
+
if result.kind_of?(String)
|
464
|
+
opt_str << result unless result.empty?
|
465
|
+
elsif result.nil?
|
466
|
+
opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
|
467
|
+
elsif result.kind_of?(Array) && 3 == result.size
|
468
|
+
opt_str << format_option(*result)
|
469
|
+
else
|
470
|
+
raise "Invalid return value #{result.inspect} from yield block "+
|
471
|
+
"attached to #to_s."
|
472
|
+
end
|
473
|
+
else
|
474
|
+
opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
|
475
|
+
end
|
476
|
+
s << opt_str.join unless opt_str.empty?
|
477
|
+
}
|
478
|
+
#s.collect! { |i| i.kind_of?(Array) && /\n+/ =~ i[0] ? i.join : f.paragraphs(i) }
|
479
|
+
[header, s].flatten.join(sep)
|
480
|
+
end
|
481
|
+
|
482
|
+
def format_option(names, opt_desc, arg_desc)
|
483
|
+
# TODO: Clean up the magic numbers
|
484
|
+
|
485
|
+
f = Text::Format.new
|
486
|
+
f.columns = @columns
|
487
|
+
f.first_indent = 4
|
488
|
+
f.body_indent = 8
|
489
|
+
f.tabstop = 4
|
490
|
+
s = ""
|
491
|
+
s << f.format("#{names.join(",")} #{arg_desc}")
|
492
|
+
#if 7 == s.last.size
|
493
|
+
if 7 == s.size
|
494
|
+
f.first_indent = 4 - 2
|
495
|
+
s.rstrip!
|
496
|
+
s << f.format(opt_desc)
|
497
|
+
#elsif 8 == s.last.size
|
498
|
+
elsif 8 == s.size
|
499
|
+
f.first_indent = 4 - 3
|
500
|
+
s.rstrip!
|
501
|
+
s << f.format(opt_desc)
|
502
|
+
else
|
503
|
+
f.first_indent = 2 * 4
|
504
|
+
s << f.format(opt_desc)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
private :format_option
|
508
|
+
|
509
|
+
end#class OptionParser
|
510
|
+
|
511
|
+
end#module CommandLine
|