OptionParser 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|