benry-cmdopt 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +29 -3
- data/MIT-LICENSE +21 -0
- data/README.md +445 -119
- data/Rakefile.rb +6 -87
- data/benry-cmdopt.gemspec +23 -21
- data/doc/benry-cmdopt.html +650 -0
- data/doc/css/style.css +160 -0
- data/lib/benry/cmdopt.rb +568 -439
- data/task/common-task.rb +138 -0
- data/task/package-task.rb +72 -0
- data/task/readme-task.rb +125 -0
- data/task/test-task.rb +81 -0
- data/test/cmdopt_test.rb +1361 -722
- metadata +22 -28
data/lib/benry/cmdopt.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
###
|
4
|
-
### $Release$
|
5
|
-
### $Copyright$
|
6
|
-
### $License$
|
5
|
+
### $Release: 2.0.0 $
|
6
|
+
### $Copyright: copyright(c) 2021 kwatch@gmail.com $
|
7
|
+
### $License: MIT License $
|
7
8
|
###
|
8
9
|
|
9
10
|
require 'date'
|
@@ -11,516 +12,644 @@ require 'set'
|
|
11
12
|
|
12
13
|
|
13
14
|
module Benry
|
15
|
+
end
|
14
16
|
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
## cmdopt.add(:help , '-h, --help' , "print help message")
|
23
|
-
## cmdopt.add(:version, ' --version', "print version")
|
24
|
-
## ## parse
|
25
|
-
## options = cmdopt.parse(ARGV) do |err|
|
26
|
-
## $stderr.puts "ERROR: #{err.message}"
|
27
|
-
## exit(1)
|
28
|
-
## end
|
29
|
-
## p options # ex: {:help => true, :version => true}
|
30
|
-
## p ARGV # options are removed from ARGV
|
31
|
-
## ## help
|
32
|
-
## if options[:help]
|
33
|
-
## puts "Usage: foobar [<options>] [<args>...]"
|
34
|
-
## puts ""
|
35
|
-
## puts "Options:"
|
36
|
-
## puts cmdopt.option_help()
|
37
|
-
## ## or
|
38
|
-
## #format = " %-20s : %s"
|
39
|
-
## #cmdopt.each_option_help {|opt, help| puts format % [opt, help] }
|
40
|
-
## end
|
41
|
-
##
|
42
|
-
## Command option parameter:
|
43
|
-
## ## required
|
44
|
-
## cmdopt.add(:file, '-f, --file=<FILE>', "filename")
|
45
|
-
## cmdopt.add(:file, ' --file=<FILE>', "filename")
|
46
|
-
## cmdopt.add(:file, '-f <FILE>' , "filename")
|
47
|
-
## ## optional
|
48
|
-
## cmdopt.add(:file, '-f, --file[=<FILE>]', "filename")
|
49
|
-
## cmdopt.add(:file, ' --file[=<FILE>]', "filename")
|
50
|
-
## cmdopt.add(:file, '-f[<FILE>]' , "filename")
|
51
|
-
##
|
52
|
-
## Validation:
|
53
|
-
## ## type
|
54
|
-
## cmdopt.add(:indent , '-i <N>', "indent width", type: Integer)
|
55
|
-
## ## pattern
|
56
|
-
## cmdopt.add(:indent , '-i <N>', "indent width", pattern: /\A\d+\z/)
|
57
|
-
## ## enum
|
58
|
-
## cmdopt.add(:indent , '-i <N>', "indent width", enum: [2, 4, 8])
|
59
|
-
## ## callback
|
60
|
-
## cmdopt.add(:indent , '-i <N>', "indent width") {|val|
|
61
|
-
## val =~ /\A\d+\z/ or
|
62
|
-
## raise "integer expected." # raise without exception class.
|
63
|
-
## val.to_i # convert argument value.
|
64
|
-
## }
|
65
|
-
##
|
66
|
-
## Available types:
|
67
|
-
## * Integer (`/\A[-+]?\d+\z/`)
|
68
|
-
## * Float (`/\A[-+]?(\d+\.\d*\|\.\d+)z/`)
|
69
|
-
## * TrueClass (`/\A(true|on|yes|false|off|no)\z/`)
|
70
|
-
## * Date (`/\A\d\d\d\d-\d\d?-\d\d?\z/`)
|
71
|
-
##
|
72
|
-
## Multiple parameters:
|
73
|
-
## cmdopt.add(:lib , '-I <NAME>', "library name") {|optdict, key, val|
|
74
|
-
## arr = optdict[key] || []
|
75
|
-
## arr << val
|
76
|
-
## arr
|
77
|
-
## }
|
78
|
-
##
|
79
|
-
## Hidden option:
|
80
|
-
## ### if help string is nil, that option is removed from help message.
|
81
|
-
## require 'benry/cmdopt'
|
82
|
-
## cmdopt = Benry::Cmdopt.new
|
83
|
-
## cmdopt.add(:verbose, '-v, --verbose', "verbose mode")
|
84
|
-
## cmdopt.add(:debug , '-d[<LEVEL>]' , nil, type: Integer) # hidden
|
85
|
-
## puts cmdopt.option_help()
|
86
|
-
## ### output ('-d' doesn't appear because help string is nil)
|
87
|
-
## # -v, --verbose : verbose mode
|
88
|
-
##
|
89
|
-
## Not supported:
|
90
|
-
## * default value
|
91
|
-
## * `--no-xxx` style option
|
92
|
-
## * bash/zsh completion
|
93
|
-
##
|
94
|
-
module Cmdopt
|
95
|
-
|
96
|
-
|
97
|
-
VERSION = '$Release: 1.1.0 $'.split()[1]
|
98
|
-
|
99
|
-
|
100
|
-
def self.new
|
101
|
-
#; [!7kkqv] creates Facade object.
|
102
|
-
return Facade.new
|
103
|
-
end
|
104
|
-
|
105
|
-
|
106
|
-
class Facade
|
107
|
-
|
108
|
-
def initialize
|
109
|
-
@schema = SCHEMA_CLASS.new
|
110
|
-
end
|
18
|
+
##
|
19
|
+
## Command option parser.
|
20
|
+
##
|
21
|
+
## See: https://github.com/kwatch/benry-ruby/tree/main/benry-cmdopt
|
22
|
+
##
|
23
|
+
module Benry::CmdOpt
|
111
24
|
|
112
|
-
def add(key, optdef, help, type: nil, pattern: nil, enum: nil, &callback)
|
113
|
-
#; [!vmb3r] defines command option.
|
114
|
-
@schema.add(key, optdef, help, type: type, pattern: pattern, enum: enum, &callback)
|
115
|
-
#; [!tu4k3] returns self.
|
116
|
-
self
|
117
|
-
end
|
118
25
|
|
119
|
-
|
120
|
-
#; [!dm4p8] returns option help message.
|
121
|
-
return @schema.option_help(width_or_format, all: all)
|
122
|
-
end
|
26
|
+
VERSION = '$Release: 2.0.0 $'.split()[1]
|
123
27
|
|
124
|
-
def each_option_help(&block)
|
125
|
-
#; [!bw9qx] yields each option definition string and help message.
|
126
|
-
@schema.each_option_help(&block)
|
127
|
-
self
|
128
|
-
end
|
129
28
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
29
|
+
def self.new()
|
30
|
+
#; [!7kkqv] creates Facade object.
|
31
|
+
return Facade.new
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class Facade
|
138
36
|
|
37
|
+
def initialize()
|
38
|
+
@schema = SCHEMA_CLASS.new
|
139
39
|
end
|
140
40
|
|
41
|
+
attr_reader :schema
|
141
42
|
|
142
|
-
|
43
|
+
def add(key, optdef, desc, *rest, type: nil, rexp: nil, pattern: nil, enum: nil, range: nil, value: nil, detail: nil, tag: nil, &callback)
|
44
|
+
rexp ||= pattern # for backward compatibility
|
45
|
+
#; [!vmb3r] defines command option.
|
46
|
+
#; [!71cvg] type, rexp, enum, and range are can be passed as positional args as well as keyword args.
|
47
|
+
@schema.add(key, optdef, desc, *rest, type: type, rexp: rexp, enum: enum, range: range, value: value, detail: detail, tag: tag, &callback)
|
48
|
+
#; [!tu4k3] returns self.
|
49
|
+
self
|
50
|
+
end
|
143
51
|
|
144
|
-
|
145
|
-
|
146
|
-
|
52
|
+
def option_help(width_or_format=nil, all: false)
|
53
|
+
#; [!dm4p8] returns option help message.
|
54
|
+
return @schema.option_help(width_or_format, all: all)
|
55
|
+
end
|
147
56
|
|
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
|
-
@items << item
|
194
|
-
item
|
57
|
+
#; [!s61vo] '#to_s' is an alias to '#option_help()'.
|
58
|
+
alias to_s option_help
|
59
|
+
|
60
|
+
def each_option_and_desc(all: false, &block)
|
61
|
+
#; [!wght5] returns enumerator object if block not given.
|
62
|
+
return @schema.each_option_and_desc(all: all) unless block_given?()
|
63
|
+
#; [!bw9qx] yields each option definition string and help message.
|
64
|
+
#; [!kunfw] yields all items (including hidden items) if `all: true` specified.
|
65
|
+
@schema.each_option_and_desc(all: all, &block)
|
66
|
+
self
|
67
|
+
end
|
68
|
+
alias each_option_help each_option_and_desc # for backward compatibility
|
69
|
+
|
70
|
+
def parse(argv, all: true, &error_handler)
|
71
|
+
#; [!7gc2m] parses command options.
|
72
|
+
#; [!no4xu] returns option values as dict.
|
73
|
+
#; [!areof] handles only OptionError when block given.
|
74
|
+
#; [!peuva] returns nil when OptionError handled.
|
75
|
+
#; [!za9at] parses options only before args when `all: false`.
|
76
|
+
parser = PARSER_CLASS.new(@schema)
|
77
|
+
return parser.parse(argv, all: all, &error_handler)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class Schema
|
84
|
+
|
85
|
+
def initialize()
|
86
|
+
@items = []
|
87
|
+
end
|
88
|
+
|
89
|
+
def dup()
|
90
|
+
#; [!lxb0o] copies self object.
|
91
|
+
other = self.class.new
|
92
|
+
other.instance_variable_set(:@items, @items.dup)
|
93
|
+
return other
|
94
|
+
end
|
95
|
+
|
96
|
+
def copy_from(other, except: [])
|
97
|
+
#; [!6six3] copy schema items from others.
|
98
|
+
#; [!vt88s] copy schema items except items specified by 'except:' kwarg.
|
99
|
+
except = [except].flatten()
|
100
|
+
other.each do |item|
|
101
|
+
@items << item unless except.include?(item.key)
|
195
102
|
end
|
103
|
+
self
|
104
|
+
end
|
196
105
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
case
|
202
|
-
when
|
203
|
-
when
|
204
|
-
when
|
106
|
+
def add(key, optdef, desc, *rest, type: nil, rexp: nil, pattern: nil, enum: nil, range: nil, value: nil, detail: nil, tag: nil, &callback)
|
107
|
+
rexp ||= pattern # for backward compatibility
|
108
|
+
#; [!kuhf9] type, rexp, enum, and range are can be passed as positional args as well as keyword args.
|
109
|
+
rest.each do |x|
|
110
|
+
case x
|
111
|
+
when Class ; type ||= x
|
112
|
+
when Regexp ; rexp ||= x
|
113
|
+
when Array, Set ; enum ||= x
|
114
|
+
when Range ; range ||= x
|
205
115
|
else
|
206
|
-
|
207
|
-
|
208
|
-
#; [!v7z4x] skips option help if help message is not specified.
|
209
|
-
#; [!to1th] includes all option help when `all` is true.
|
210
|
-
buf = []
|
211
|
-
width = nil
|
212
|
-
each_option_help do |opt, help|
|
213
|
-
#buf << format % [opt, help] << "\n" if help || all
|
214
|
-
if help
|
215
|
-
#; [!848rm] supports multi-lines help message.
|
216
|
-
n = 0
|
217
|
-
help.each_line do |line|
|
218
|
-
if (n += 1) == 1
|
219
|
-
buf << format % [opt, line.chomp] << "\n"
|
220
|
-
else
|
221
|
-
width ||= (format % ['', '']).length
|
222
|
-
buf << (' ' * width) << line.chomp << "\n"
|
223
|
-
end
|
224
|
-
end
|
225
|
-
elsif all
|
226
|
-
buf << format % [opt, ''] << "\n"
|
227
|
-
end
|
116
|
+
#; [!e3emy] raises error when positional arg is not one of class, regexp, array, nor range.
|
117
|
+
raise _error("#{x.inspect}: Expected one of class, regexp, array or range, but got #{x.class.name}.")
|
228
118
|
end
|
229
|
-
return buf.join()
|
230
119
|
end
|
120
|
+
#; [!rhhji] raises SchemaError when key is not a Symbol.
|
121
|
+
key.nil? || key.is_a?(Symbol) or
|
122
|
+
raise _error("add(#{key.inspect}, #{optdef.inspect}): The first arg should be a Symbol as an option key.")
|
123
|
+
#; [!vq6eq] raises SchemaError when help message is missing."
|
124
|
+
desc.nil? || desc.is_a?(String) or
|
125
|
+
raise _error("add(#{key.inspect}, #{optdef.inspect}): Help message required as 3rd argument.")
|
126
|
+
#; [!7hi2d] takes command option definition string.
|
127
|
+
short, long, param, required = parse_optdef(optdef)
|
128
|
+
#; [!p9924] option key is omittable only when long option specified.
|
129
|
+
#; [!jtp7z] raises SchemaError when key is nil and no long option.
|
130
|
+
key || long or
|
131
|
+
raise _error("add(#{key.inspect}, #{optdef.inspect}): Long option required when option key (1st arg) not specified.")
|
132
|
+
#; [!rpl98] when long option is 'foo-bar' then key name is ':foo_bar'.
|
133
|
+
key ||= long.gsub(/-/, '_').intern
|
134
|
+
#; [!97sn0] raises SchemaError when ',' is missing between short and long options.
|
135
|
+
if long.nil? && param =~ /\A--/
|
136
|
+
raise _error("add(#{key.inspect}, #{optdef.inspect}): Missing ',' between short option and long options.")
|
137
|
+
end
|
138
|
+
#; [!yht0v] keeps command option definitions.
|
139
|
+
item = SchemaItem.new(key, optdef, desc, short, long, param, required,
|
140
|
+
type: type, rexp: rexp, enum: enum, range: range, value: value, detail: detail, tag: tag, &callback)
|
141
|
+
@items << item
|
142
|
+
item
|
143
|
+
end
|
231
144
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
145
|
+
def option_help(width_or_format=nil, all: false)
|
146
|
+
#; [!0aq0i] can take integer as width.
|
147
|
+
#; [!pcsah] can take format string.
|
148
|
+
#; [!dndpd] detects option width automatically when nothing specified.
|
149
|
+
case width_or_format
|
150
|
+
when nil ; format = _default_format()
|
151
|
+
when Integer; format = " %-#{width_or_format}s : %s"
|
152
|
+
when String ; format = width_or_format
|
153
|
+
else
|
154
|
+
raise ArgumentError.new("#{width_or_format.inspect}: Width (integer) or format (string) expected.")
|
155
|
+
end
|
156
|
+
#; [!v7z4x] skips option help if help message is not specified.
|
157
|
+
#; [!to1th] includes all option help when `all` is true.
|
158
|
+
#; [!a4qe4] option should not be hidden if description is empty string.
|
159
|
+
sb = []
|
160
|
+
width = nil; indent = nil
|
161
|
+
each_option_and_desc(all: all) do |opt, desc, detail|
|
162
|
+
sb << format % [opt, desc || ""] << "\n"
|
163
|
+
#; [!848rm] supports multi-lines help message.
|
164
|
+
if detail
|
165
|
+
width ||= (format % ['', '']).length
|
166
|
+
indent ||= ' ' * width
|
167
|
+
sb << detail.gsub(/^/, indent)
|
168
|
+
sb << "\n" unless detail.end_with?("\n")
|
236
169
|
end
|
237
|
-
#; [!zbxyv] returns self.
|
238
|
-
self
|
239
170
|
end
|
171
|
+
return sb.join()
|
172
|
+
end
|
240
173
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
174
|
+
#; [!rrapd] '#to_s' is an alias to '#option_help()'.
|
175
|
+
alias to_s option_help
|
176
|
+
|
177
|
+
def each_option_and_desc(all: false, &block)
|
178
|
+
#; [!03sux] returns enumerator object if block not given.
|
179
|
+
return to_enum(:each_option_and_desc, all: all) unless block_given?()
|
180
|
+
#; [!4b911] yields each optin definition str and help message.
|
181
|
+
@items.each do |item|
|
182
|
+
#; [!cl8zy] when 'all' flag is false, not yield hidden items.
|
183
|
+
#; [!tc4bk] when 'all' flag is true, yields even hidden items.
|
184
|
+
yield item.optdef, item.desc, item.detail if all || ! item.hidden?
|
245
185
|
end
|
186
|
+
#; [!zbxyv] returns self.
|
187
|
+
self
|
188
|
+
end
|
189
|
+
alias each_option_help each_option_and_desc # for backward compatibility
|
246
190
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
191
|
+
def each(&block) # :nodoc:
|
192
|
+
#; [!y4k1c] yields each option item.
|
193
|
+
@items.each(&block)
|
194
|
+
end
|
252
195
|
|
253
|
-
|
196
|
+
def empty?(all: true)
|
197
|
+
#; [!um8am] returns false if any item exists, else returns true.
|
198
|
+
#; [!icvm1] ignores hidden items if 'all: false' kwarg specified.
|
199
|
+
@items.each {|item| return false if all || ! item.hidden? }
|
200
|
+
return true
|
201
|
+
end
|
254
202
|
|
255
|
-
|
256
|
-
|
257
|
-
|
203
|
+
def get(key)
|
204
|
+
#; [!3wjfp] finds option item object by key.
|
205
|
+
#; [!0spll] returns nil if key not found.
|
206
|
+
return @items.find {|item| item.key == key }
|
207
|
+
end
|
258
208
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
short, long, param1, param2 = $1, $2, $3, $4
|
267
|
-
when /\A[ \t]*-(\w)(?:[ \t]+(\S+)|\[(\S+)\])?\z/
|
268
|
-
short, long, param1, param2 = $1, nil, $2, $3
|
269
|
-
when /\A[ \t]*--(\w[-\w]*)(?:=(\S*?)|\[=(\S*?)\])?\z/
|
270
|
-
short, long, param1, param2 = nil, $1, $2, $3
|
271
|
-
when /(--\w[-\w])*[ \t]+(\S+)/
|
272
|
-
raise error("#{optdef}: invalid option definition (use '#{$1}=#{$2}' instead of '#{$1} #{$2}').")
|
273
|
-
else
|
274
|
-
raise error("#{optdef}: invalid option definition.")
|
275
|
-
end
|
276
|
-
return short, long, param1 || param2, !!param2
|
277
|
-
end
|
209
|
+
def delete(key)
|
210
|
+
#; [!l86rb] deletes option item corresponding to key.
|
211
|
+
#; [!rq0aa] returns deleted item.
|
212
|
+
item = get(key)
|
213
|
+
@items.delete_if {|item| item.key == key }
|
214
|
+
return item
|
215
|
+
end
|
278
216
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
217
|
+
def find_short_option(short)
|
218
|
+
#; [!b4js1] returns option definition matched to short name.
|
219
|
+
#; [!s4d1y] returns nil when nothing found.
|
220
|
+
return @items.find {|item| item.short == short }
|
221
|
+
end
|
222
|
+
|
223
|
+
def find_long_option(long)
|
224
|
+
#; [!atmf9] returns option definition matched to long name.
|
225
|
+
#; [!6haoo] returns nil when nothing found.
|
226
|
+
return @items.find {|item| item.long == long }
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def _error(msg)
|
232
|
+
return SchemaError.new(msg)
|
233
|
+
end
|
234
|
+
|
235
|
+
def parse_optdef(optdef)
|
236
|
+
#; [!qw0ac] parses command option definition string.
|
237
|
+
#; [!ae733] parses command option definition which has a required param.
|
238
|
+
#; [!4h05c] parses command option definition which has an optional param.
|
239
|
+
#; [!b7jo3] raises SchemaError when command option definition is invalid.
|
240
|
+
case optdef
|
241
|
+
when /\A[ \t]*-(\w),[ \t]*--(\w[-\w]*)(?:=(\S*?)|\[=(\S*?)\])?\z/
|
242
|
+
short, long, param1, param2 = $1, $2, $3, $4
|
243
|
+
when /\A[ \t]*-(\w)(?:[ \t]+(\S+)|\[(\S+)\])?\z/
|
244
|
+
short, long, param1, param2 = $1, nil, $2, $3
|
245
|
+
when /\A[ \t]*--(\w[-\w]*)(?:=(\S*?)|\[=(\S*?)\])?\z/
|
246
|
+
short, long, param1, param2 = nil, $1, $2, $3
|
247
|
+
when /(--\w[-\w])*[ \t]+(\S+)/
|
248
|
+
raise _error("#{optdef}: Invalid option definition (use '#{$1}=#{$2}' instead of '#{$1} #{$2}').")
|
249
|
+
else
|
250
|
+
raise _error("#{optdef}: Invalid option definition.")
|
291
251
|
end
|
252
|
+
required = param1 ? true : param2 ? false : nil
|
253
|
+
return short, long, (param1 || param2), required
|
254
|
+
end
|
292
255
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
256
|
+
def _default_format(min_width=nil, max_width=35)
|
257
|
+
#; [!bmr7d] changes min_with according to options.
|
258
|
+
min_width ||= _preferred_option_width()
|
259
|
+
#; [!hr45y] detects preffered option width.
|
260
|
+
w = 0
|
261
|
+
each_option_help do |opt, _|
|
262
|
+
w = opt.length if w < opt.length
|
300
263
|
end
|
264
|
+
w = min_width if w < min_width
|
265
|
+
w = max_width if w > max_width
|
266
|
+
#; [!kkh9t] returns format string.
|
267
|
+
return " %-#{w}s : %s"
|
268
|
+
end
|
301
269
|
|
270
|
+
def _preferred_option_width()
|
271
|
+
#; [!kl91t] shorten option help min width when only single options which take no arg.
|
272
|
+
#; [!0koqb] widen option help min width when any option takes an arg.
|
273
|
+
#; [!kl91t] widen option help min width when long option exists.
|
274
|
+
long_p = @items.any? {|x| x.desc && x.long && x.param }
|
275
|
+
short_p = @items.all? {|x| x.desc && !x.long && !x.param }
|
276
|
+
return short_p ? 8 : long_p ? 20 : 14
|
302
277
|
end
|
303
278
|
|
279
|
+
end
|
304
280
|
|
305
|
-
class SchemaItem # avoid Struct
|
306
281
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
282
|
+
class SchemaItem # avoid Struct
|
283
|
+
|
284
|
+
def initialize(key, optdef, desc, short, long, param, required, type: nil, rexp: nil, pattern: nil, enum: nil, range: nil, detail: nil, value: nil, tag: nil, &callback)
|
285
|
+
rexp ||= pattern # for backward compatibility
|
286
|
+
_init_validation(param, required, type, rexp, enum, range, value)
|
287
|
+
@key = key unless nil == key
|
288
|
+
@optdef = optdef unless nil == optdef
|
289
|
+
@desc = desc unless nil == desc
|
290
|
+
@short = short unless nil == short
|
291
|
+
@long = long unless nil == long
|
292
|
+
@param = param unless nil == param
|
293
|
+
@required = required unless nil == required
|
294
|
+
@type = type unless nil == type
|
295
|
+
@rexp = rexp unless nil == rexp
|
296
|
+
@enum = enum unless nil == enum
|
297
|
+
@range = range unless nil == range
|
298
|
+
@detail = detail unless nil == detail
|
299
|
+
@value = value unless nil == value
|
300
|
+
@tag = tag unless nil == tag
|
301
|
+
@callback = callback unless nil == callback
|
302
|
+
#; [!nn4cp] freezes enum object.
|
303
|
+
@enum.freeze() if @enum
|
304
|
+
end
|
320
305
|
|
321
|
-
|
322
|
-
|
306
|
+
attr_reader :key, :optdef, :desc, :short, :long, :param, :type, :rexp, :enum, :range, :detail, :value, :tag, :callback
|
307
|
+
alias pattern rexp # for backward compatibility
|
308
|
+
alias help desc # for backward compatibility
|
323
309
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
#; [!j4fuz] calls type-specific callback when type specified.
|
331
|
-
if @type && val != true
|
332
|
-
proc_ = PARAM_TYPES[@type]
|
333
|
-
val = proc_.call(val)
|
334
|
-
end
|
335
|
-
#; [!5jrdf] raises RuntimeError when value not in enum.
|
336
|
-
if @enum && val != true
|
337
|
-
@enum.include?(val) or
|
338
|
-
raise "expected one of #{@enum.join('/')}."
|
339
|
-
end
|
340
|
-
#; [!jn9z3] calls callback when callback specified.
|
341
|
-
#; [!iqalh] calls callback with different number of args according to arity.
|
342
|
-
if @callback
|
343
|
-
n_args = @callback.arity
|
344
|
-
val = n_args == 1 ? @callback.call(val) \
|
345
|
-
: @callback.call(optdict, @key, val)
|
346
|
-
end
|
347
|
-
#; [!x066l] returns new value.
|
348
|
-
return val
|
349
|
-
end
|
310
|
+
def required?()
|
311
|
+
#; [!svxny] returns nil if option takes no arguments.
|
312
|
+
#; [!uwbgc] returns false if argument is optional.
|
313
|
+
#; [!togcx] returns true if argument is required.
|
314
|
+
return ! @param ? nil : !! @required
|
315
|
+
end
|
350
316
|
|
317
|
+
def arg_requireness()
|
318
|
+
#; [!kmo28] returns :none if option takes no arguments.
|
319
|
+
#; [!owpba] returns :optional if argument is optional.
|
320
|
+
#; [!s8gxl] returns :required if argument is required.
|
321
|
+
return :none if ! @param
|
322
|
+
return :required if @required
|
323
|
+
return :optional
|
351
324
|
end
|
352
325
|
|
326
|
+
def hidden?()
|
327
|
+
#; [!h0uxs] returns true if desc is nil.
|
328
|
+
#; [!su00g] returns true if key starts with '_'.
|
329
|
+
#; [!28vzx] returns false if else.
|
330
|
+
return @desc == nil || @key.to_s.start_with?('_')
|
331
|
+
end
|
353
332
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
val.
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
333
|
+
def validate_and_convert(val, optdict)
|
334
|
+
#; [!h0s0o] raises RuntimeError when value not matched to pattern.
|
335
|
+
if @rexp && val != true
|
336
|
+
val =~ @rexp or
|
337
|
+
raise "Pattern unmatched."
|
338
|
+
end
|
339
|
+
#; [!j4fuz] calls type-specific callback when type specified.
|
340
|
+
if @type && val != true
|
341
|
+
proc_ = PARAM_TYPES[@type]
|
342
|
+
val = proc_.call(val)
|
343
|
+
end
|
344
|
+
#; [!5jrdf] raises RuntimeError when value not in enum.
|
345
|
+
if @enum && val != true
|
346
|
+
@enum.include?(val) or
|
347
|
+
raise "Expected one of #{@enum.join('/')}."
|
348
|
+
end
|
349
|
+
#; [!5falp] raise RuntimeError when value not in range.
|
350
|
+
#; [!a0rej] supports endless range.
|
351
|
+
if @range && val != true
|
352
|
+
r = @range
|
353
|
+
r.begin == nil || r.begin <= val or (
|
354
|
+
raise "Positive value (>= 0) expected." if r.begin == 0
|
355
|
+
raise "Positive value (>= 1) expected." if r.begin == 1
|
356
|
+
raise "Too small (min: #{r.begin.inspect})"
|
357
|
+
)
|
358
|
+
r.end == nil || val <= r.end or
|
359
|
+
raise "Too large (max: #{r.end.inspect})"
|
360
|
+
end
|
361
|
+
#; [!jn9z3] calls callback when callback specified.
|
362
|
+
#; [!iqalh] calls callback with different number of args according to arity.
|
363
|
+
if @callback
|
364
|
+
n_args = @callback.arity
|
365
|
+
val = n_args == 1 ? @callback.call(val) \
|
366
|
+
: @callback.call(optdict, @key, val)
|
367
|
+
end
|
368
|
+
#; [!eafem] returns default value (if specified) instead of true value.
|
369
|
+
return @value if val == true && @value != nil
|
370
|
+
#; [!x066l] returns new value.
|
371
|
+
return val
|
372
|
+
end
|
373
|
+
|
374
|
+
private
|
375
|
+
|
376
|
+
def _error(msg)
|
377
|
+
return SchemaError.new(msg)
|
378
|
+
end
|
379
|
+
|
380
|
+
def _init_validation(param, required, type, rexp, enum, range, value)
|
381
|
+
#; [!wy2iv] when 'type:' specified...
|
382
|
+
if type
|
383
|
+
#; [!7xmr5] raises SchemaError when type is not registered.
|
384
|
+
PARAM_TYPES.key?(type) or
|
385
|
+
raise _error("#{type.inspect}: Unregistered type.")
|
386
|
+
#; [!s2aaj] raises SchemaError when option has no params but type specified.
|
387
|
+
#; [!sz8x2] not raise error when no params but value specified.
|
388
|
+
#; [!70ogf] not raise error when no params but TrueClass specified.
|
389
|
+
param || value != nil || type == TrueClass or
|
390
|
+
raise _error("#{type.inspect}: Type specified in spite of option has no params.")
|
391
|
+
end
|
392
|
+
#; [!6y8s2] when 'rexp:' specified...
|
393
|
+
if rexp
|
394
|
+
#; [!bi2fh] raises SchemaError when pattern is not a regexp.
|
395
|
+
rexp.is_a?(Regexp) or
|
396
|
+
raise _error("#{rexp.inspect}: Regexp pattern expected.")
|
397
|
+
#; [!01fmt] raises SchmeaError when option has no params but pattern specified.
|
398
|
+
param or
|
399
|
+
raise _error("#{rexp.inspect}: Regexp pattern specified in spite of option has no params.")
|
400
|
+
end
|
401
|
+
#; [!5nrvq] when 'enum:' specified...
|
402
|
+
if enum
|
403
|
+
#; [!melyd] raises SchemaError when enum is not an Array nor Set.
|
404
|
+
enum.is_a?(Array) || enum.is_a?(Set) or
|
405
|
+
raise _error("#{enum.inspect}: Array or set expected.")
|
406
|
+
#; [!xqed8] raises SchemaError when enum specified for no param option.
|
407
|
+
param or
|
408
|
+
raise _error("#{enum.inspect}: Enum specified in spite of option has no params.")
|
409
|
+
#; [!zuthh] raises SchemaError when enum element value is not instance of type class.
|
410
|
+
enum.each do |x|
|
411
|
+
x.is_a?(type) or
|
412
|
+
raise _error("#{enum.inspect}: Enum element value should be instance of #{type.name}, but #{x.inspect} is not.")
|
413
|
+
end if type
|
414
|
+
end
|
415
|
+
#; [!hk4nw] when 'range:' specified...
|
416
|
+
if range
|
417
|
+
#; [!z20ky] raises SchemaError when range is not a Range object.
|
418
|
+
range.is_a?(Range) or
|
419
|
+
raise _error("#{range.inspect}: Range object expected.")
|
420
|
+
#; [!gp025] raises SchemaError when range specified with `type: TrueClass`.
|
421
|
+
if type == TrueClass
|
422
|
+
raise _error("#{range.inspect}: Range is not available with `type: TrueClass`.")
|
423
|
+
#; [!7njd5] range beginning/end value should be expected type.
|
381
424
|
else
|
382
|
-
|
425
|
+
#; [!uymig] range object can be endless.
|
426
|
+
type_ = type || String
|
427
|
+
ok1 = range.begin == nil || range.begin.is_a?(type_)
|
428
|
+
ok2 = range.end == nil || range.end.is_a?(type_)
|
429
|
+
ok1 && ok2 or
|
430
|
+
raise _error("#{range.inspect}: Range value should be #{type_.name}, but not.")
|
383
431
|
end
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
#; [!
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
432
|
+
end
|
433
|
+
#; [!a0g52] when 'value:' specified...
|
434
|
+
if value != nil
|
435
|
+
#; [!435t6] raises SchemaError when 'value:' is specified on argument-required option.
|
436
|
+
! required or
|
437
|
+
raise _error("#{value.inspect}: 'value:' is meaningless when option has required argument (hint: change to optional argument instead).")
|
438
|
+
if type == TrueClass
|
439
|
+
#; [!6vwqv] raises SchemaError when type is TrueClass but value is not true nor false.
|
440
|
+
value == true || value == false or
|
441
|
+
raise _error("#{value.inspect}: Value should be true or false when `type: TrueClass` specified.")
|
442
|
+
elsif type
|
443
|
+
#; [!c6i2o] raises SchemaError when value is not a kind of type.
|
444
|
+
value.is_a?(type) or
|
445
|
+
raise _error("Type mismatched between `type: #{type.name}` and `value: #{value.inspect}`.")
|
446
|
+
else
|
447
|
+
#; [!lnhp6] not raise error when type is not specified.
|
448
|
+
end
|
449
|
+
if enum
|
450
|
+
#; [!6xb8o] value should be included in enum values.
|
451
|
+
enum.include?(value) or
|
452
|
+
raise _error("#{value}: Value should be included in enum values, but not.")
|
395
453
|
end
|
396
|
-
|
397
|
-
|
454
|
+
end
|
455
|
+
end
|
398
456
|
|
457
|
+
end
|
399
458
|
|
400
|
-
class Parser
|
401
459
|
|
402
|
-
|
403
|
-
|
460
|
+
PARAM_TYPES = {
|
461
|
+
String => proc {|val|
|
462
|
+
val
|
463
|
+
},
|
464
|
+
Integer => proc {|val|
|
465
|
+
#; [!6t8cs] converts value into integer.
|
466
|
+
#; [!nzwc9] raises error when failed to convert value into integer.
|
467
|
+
val =~ /\A[-+]?\d+\z/ or
|
468
|
+
raise "Integer expected."
|
469
|
+
val.to_i
|
470
|
+
},
|
471
|
+
Float => proc {|val|
|
472
|
+
#; [!gggy6] converts value into float.
|
473
|
+
#; [!t4elj] raises error when faield to convert value into float.
|
474
|
+
val =~ /\A[-+]?(\d+\.\d*|\.\d+)\z/ or
|
475
|
+
raise "Float expected."
|
476
|
+
val.to_f
|
477
|
+
},
|
478
|
+
TrueClass => proc {|val|
|
479
|
+
#; [!47kx4] converts 'true'/'on'/'yes' into true.
|
480
|
+
#; [!3n810] converts 'false'/'off'/'no' into false.
|
481
|
+
#; [!h8ayh] raises error when failed to convert value into true nor false.
|
482
|
+
case val
|
483
|
+
when /\A(?:true|on|yes)\z/i ; true
|
484
|
+
when /\A(?:false|off|no)\z/i ; false
|
485
|
+
else
|
486
|
+
raise "Boolean expected."
|
487
|
+
end
|
488
|
+
},
|
489
|
+
Date => proc {|val|
|
490
|
+
#; [!sru5j] converts 'YYYY-MM-DD' into date object.
|
491
|
+
#; [!h9q9y] raises error when failed to convert into date object.
|
492
|
+
#; [!i4ui8] raises error when specified date not exist.
|
493
|
+
val =~ /\A(\d\d\d\d)-(\d\d?)-(\d\d?)\z/ or
|
494
|
+
raise "Invalid date format (ex: '2000-01-01')"
|
495
|
+
begin
|
496
|
+
Date.new($1.to_i, $2.to_i, $3.to_i)
|
497
|
+
rescue ArgumentError => ex
|
498
|
+
raise "Date not exist."
|
404
499
|
end
|
500
|
+
},
|
501
|
+
}
|
405
502
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
503
|
+
|
504
|
+
class Parser
|
505
|
+
|
506
|
+
def initialize(schema)
|
507
|
+
@schema = schema
|
508
|
+
end
|
509
|
+
|
510
|
+
def parse(argv, all: true, &error_handler)
|
511
|
+
optdict = new_options_dict()
|
512
|
+
index = 0
|
513
|
+
while index < argv.length
|
514
|
+
#; [!5s5b6] treats '-' as an argument, not an option.
|
515
|
+
if argv[index] =~ /\A-/ && argv[index] != "-"
|
516
|
+
optstr = argv.delete_at(index)
|
517
|
+
#; [!q8356] parses options even after arguments when `all: true`.
|
518
|
+
elsif all
|
519
|
+
index += 1
|
520
|
+
next
|
521
|
+
#; [!ryra3] doesn't parse options after arguments when `all: false`.
|
522
|
+
else
|
523
|
+
break
|
524
|
+
end
|
525
|
+
#; [!y04um] skips rest options when '--' found in argv.
|
526
|
+
if optstr == '--'
|
527
|
+
break
|
528
|
+
elsif optstr =~ /\A--/
|
529
|
+
#; [!uh7j8] parses long options.
|
530
|
+
parse_long_option(optstr, optdict)
|
531
|
+
else
|
532
|
+
#; [!nwnjc] parses short options.
|
533
|
+
parse_short_options(optstr, optdict) { argv.delete_at(index) }
|
420
534
|
end
|
421
|
-
#; [!3wmsy] returns command option values as a dict.
|
422
|
-
return optdict
|
423
|
-
rescue OptionError => ex
|
424
|
-
#; [!qpuxh] handles only OptionError when block given.
|
425
|
-
raise unless block_given?()
|
426
|
-
yield ex
|
427
|
-
#; [!dhpw1] returns nil when OptionError handled.
|
428
|
-
nil
|
429
535
|
end
|
536
|
+
#; [!3wmsy] returns command option values as a dict.
|
537
|
+
return optdict
|
538
|
+
rescue OptionError => ex
|
539
|
+
#; [!qpuxh] handles only OptionError when block given.
|
540
|
+
raise unless block_given?()
|
541
|
+
yield ex
|
542
|
+
#; [!dhpw1] returns nil when OptionError handled.
|
543
|
+
nil
|
544
|
+
end
|
545
|
+
|
546
|
+
def _error(msg)
|
547
|
+
return OptionError.new(msg)
|
548
|
+
end
|
430
549
|
|
431
|
-
|
432
|
-
|
550
|
+
protected
|
551
|
+
|
552
|
+
def parse_long_option(optstr, optdict)
|
553
|
+
#; [!3i994] raises OptionError when invalid long option format.
|
554
|
+
optstr =~ /\A--(\w[-\w]*)(?:=(.*))?\z/ or
|
555
|
+
raise _error("#{optstr}: Invalid long option.")
|
556
|
+
name = $1; val = $2
|
557
|
+
#; [!1ab42] invokes error handler method when unknown long option.
|
558
|
+
#; [!er7h4] default behavior is to raise OptionError when unknown long option.
|
559
|
+
item = @schema.find_long_option(name) or
|
560
|
+
return handle_unknown_long_option(optstr, name, val)
|
561
|
+
#; [!2jd9w] raises OptionError when no arguments specified for arg required long option.
|
562
|
+
#; [!qyq8n] raises optionError when an argument specified for no arg long option.
|
563
|
+
case item.arg_requireness()
|
564
|
+
when :none # no arguments
|
565
|
+
val == nil or raise _error("#{optstr}: Unexpected argument.")
|
566
|
+
when :required # argument required
|
567
|
+
val or raise _error("#{optstr}: Argument required.")
|
568
|
+
when :optional # optonal argument
|
569
|
+
# do nothing
|
570
|
+
else
|
571
|
+
raise "** internal error"
|
433
572
|
end
|
573
|
+
#; [!o596x] validates argument value.
|
574
|
+
val ||= true
|
575
|
+
begin
|
576
|
+
val = item.validate_and_convert(val, optdict)
|
577
|
+
rescue RuntimeError => ex
|
578
|
+
raise _error("#{optstr}: #{ex.message}")
|
579
|
+
end
|
580
|
+
optdict[item.key] = val
|
581
|
+
end
|
434
582
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
val
|
583
|
+
def parse_short_options(optstr, optdict, &block)
|
584
|
+
n = optstr.length
|
585
|
+
i = 0
|
586
|
+
while (i += 1) < n
|
587
|
+
char = optstr[i]
|
588
|
+
#; [!4eh49] raises OptionError when unknown short option specified.
|
589
|
+
item = @schema.find_short_option(char) or
|
590
|
+
raise _error("-#{char}: Unknown option.")
|
591
|
+
#
|
592
|
+
case item.arg_requireness()
|
593
|
+
when :none # no arguments
|
594
|
+
val = true
|
595
|
+
when :required # argument required
|
596
|
+
#; [!utdbf] raises OptionError when argument required but not specified.
|
597
|
+
#; [!f63hf] short option arg can be specified without space separator.
|
598
|
+
val = i+1 < n ? optstr[(i+1)..-1] : yield or
|
599
|
+
raise _error("-#{char}: Argument required.")
|
600
|
+
i = n
|
601
|
+
when :optional # optonal argument
|
602
|
+
#; [!yjq6b] optional arg should be specified without space separator.
|
603
|
+
#; [!wape4] otpional arg can be omit.
|
604
|
+
val = i+1 < n ? optstr[(i+1)..-1] : true
|
605
|
+
i = n
|
451
606
|
else
|
452
|
-
|
607
|
+
raise "** internal error"
|
453
608
|
end
|
454
|
-
#; [!
|
455
|
-
val ||= true
|
609
|
+
#; [!yu0kc] validates short option argument.
|
456
610
|
begin
|
457
611
|
val = item.validate_and_convert(val, optdict)
|
458
612
|
rescue RuntimeError => ex
|
459
|
-
|
460
|
-
|
461
|
-
optdict[item.key] = val
|
462
|
-
end
|
463
|
-
|
464
|
-
def parse_short_options(optstr, optdict, argv)
|
465
|
-
n = optstr.length
|
466
|
-
i = 0
|
467
|
-
while (i += 1) < n
|
468
|
-
char = optstr[i]
|
469
|
-
#; [!4eh49] raises OptionError when unknown short option specified.
|
470
|
-
item = @schema.find_short_option(char) or
|
471
|
-
raise error("-#{char}: unknown option.")
|
472
|
-
#
|
473
|
-
if !item.param
|
474
|
-
val = true
|
475
|
-
elsif !item.optional_param?
|
476
|
-
#; [!utdbf] raises OptionError when argument required but not specified.
|
477
|
-
#; [!f63hf] short option arg can be specified without space separator.
|
478
|
-
val = i+1 < n ? optstr[(i+1)..-1] : argv.shift or
|
479
|
-
raise error("-#{char}: argument required.")
|
480
|
-
i = n
|
613
|
+
if val == true
|
614
|
+
raise _error("-#{char}: #{ex.message}")
|
481
615
|
else
|
482
|
-
|
483
|
-
|
484
|
-
val = i+1 < n ? optstr[(i+1)..-1] : true
|
485
|
-
i = n
|
616
|
+
sp = item.required? ? ' ' : ''
|
617
|
+
raise _error("-#{char}#{sp}#{val}: #{ex.message}")
|
486
618
|
end
|
487
|
-
#; [!yu0kc] validates short option argument.
|
488
|
-
begin
|
489
|
-
val = item.validate_and_convert(val, optdict)
|
490
|
-
rescue RuntimeError => ex
|
491
|
-
if val == true
|
492
|
-
raise error("-#{char}: #{ex.message}")
|
493
|
-
else
|
494
|
-
s = item.optional_param? ? '' : ' '
|
495
|
-
raise error("-#{char}#{s}#{val}: #{ex.message}")
|
496
|
-
end
|
497
|
-
end
|
498
|
-
optdict[item.key] = val
|
499
619
|
end
|
620
|
+
optdict[item.key] = val
|
500
621
|
end
|
622
|
+
end
|
501
623
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
end
|
506
|
-
|
624
|
+
def new_options_dict()
|
625
|
+
#; [!vm6h0] returns new hash object.
|
626
|
+
return OPTIONS_CLASS.new
|
507
627
|
end
|
508
628
|
|
629
|
+
def handle_unknown_long_option(optstr, name, val)
|
630
|
+
#; [!0q78a] raises OptionError.
|
631
|
+
raise _error("#{optstr}: Unknown long option.")
|
632
|
+
end
|
509
633
|
|
510
|
-
|
511
|
-
SCHEMA_CLASS = Schema
|
512
|
-
PARSER_CLASS = Parser
|
634
|
+
end
|
513
635
|
|
514
636
|
|
515
|
-
|
516
|
-
|
637
|
+
OPTIONS_CLASS = Hash
|
638
|
+
SCHEMA_CLASS = Schema
|
639
|
+
PARSER_CLASS = Parser
|
517
640
|
|
518
641
|
|
519
|
-
|
520
|
-
|
642
|
+
class SchemaError < StandardError
|
643
|
+
end
|
521
644
|
|
522
645
|
|
646
|
+
class OptionError < StandardError
|
523
647
|
end
|
524
648
|
|
525
649
|
|
526
650
|
end
|
651
|
+
|
652
|
+
|
653
|
+
module Benry
|
654
|
+
Cmdopt = CmdOpt # for backawrd compatibility
|
655
|
+
end
|