xoptparse 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/xoptparse.rb +103 -186
- data/lib/xoptparse/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed8ba0e138b8c1d6e68ca65d8e022ef51f3456647121207904074ffa8d53ac9f
|
4
|
+
data.tar.gz: e3ffc6e2caa748fadeb8b9a6f3129491d2997c8edf89780689bc6eac02c07489
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f160dfefb34a43a2b9f60ec775679e810613665edba3138857836467285eb5d8ce58c664687bd823322c619ea32a8c84ddab328b7e6a432c5f784dfffdff9727
|
7
|
+
data.tar.gz: '0950deea41a448199bc90aa1fb65365940fee26c3e3dd994c27d62e8e5677aaaf160e2234a91e24539ae36ecbc498f17aded45674e50e694c1ce4599c45ed1d5'
|
data/Gemfile.lock
CHANGED
data/lib/xoptparse.rb
CHANGED
@@ -4,108 +4,45 @@ require 'xoptparse/version'
|
|
4
4
|
require 'optparse'
|
5
5
|
|
6
6
|
class XOptionParser < ::OptionParser
|
7
|
-
|
8
|
-
def valid_arg_switch_ranges?(ranges)
|
9
|
-
ranges.inject(0) do |pt, r| # prev_type = req: 0, opt: 1, rest: 2, after_req: 3
|
10
|
-
t = r.end.nil? ? 2 : 1 - r.begin
|
11
|
-
next pt.zero? ? 0 : 3 if t.zero?
|
12
|
-
next t if pt < 2 && pt <= t
|
13
|
-
|
14
|
-
return false
|
15
|
-
end
|
16
|
-
true
|
17
|
-
end
|
18
|
-
|
19
|
-
def calc_arg_switch_ranges_counts(ranges)
|
20
|
-
req_count = 0
|
21
|
-
opt_count = 0
|
22
|
-
rest_req_count = nil
|
23
|
-
last_req_count = 0
|
24
|
-
ranges.each do |r|
|
25
|
-
if r.end.nil?
|
26
|
-
rest_req_count = r.begin
|
27
|
-
elsif r.begin.zero?
|
28
|
-
opt_count += 1
|
29
|
-
elsif opt_count.positive? || rest_req_count
|
30
|
-
last_req_count += 1
|
31
|
-
else
|
32
|
-
req_count += 1
|
33
|
-
end
|
34
|
-
end
|
35
|
-
[req_count, opt_count, rest_req_count, last_req_count]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
attr_reader :description
|
40
|
-
|
41
|
-
def initialize(description = nil, *args)
|
7
|
+
def initialize(description = nil, *args, &block)
|
42
8
|
@commands = {}
|
43
|
-
@arg_stack = [[], []]
|
44
|
-
@description = description
|
45
9
|
@banner_usage = 'Usage: '
|
46
10
|
@banner_options = '[options]'
|
47
11
|
@banner_command = '<command>'
|
48
|
-
super(nil, *args)
|
12
|
+
super(nil, *args) do |opt|
|
13
|
+
if description
|
14
|
+
opt.separator ''
|
15
|
+
opt.separator description
|
16
|
+
end
|
17
|
+
block&.call(opt)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def select(*args, &block)
|
22
|
+
Enumerator.new do |y|
|
23
|
+
visit(:each_option) { |el| y << el }
|
24
|
+
end.select(*args, &block)
|
49
25
|
end
|
26
|
+
private :select
|
50
27
|
|
51
28
|
def no_options
|
52
|
-
|
53
|
-
true
|
29
|
+
select { |sw| sw.is_a?(::OptionParser::Switch) }.all? { |sw| !(sw.short || sw.long) }
|
54
30
|
end
|
55
31
|
private :no_options
|
56
32
|
|
57
|
-
def banner
|
33
|
+
def banner
|
58
34
|
return @banner if @banner
|
59
35
|
|
60
36
|
banner = +"#{@banner_usage}#{program_name}"
|
61
37
|
banner << " #{@banner_options}" unless no_options
|
62
38
|
visit(:add_banner, banner)
|
63
39
|
banner << " #{@banner_command}" unless @commands.empty?
|
64
|
-
@arg_stack.flatten(1).each do |sw|
|
65
|
-
banner << " #{sw.short.first}"
|
66
|
-
end
|
67
|
-
banner << "\n\n#{description}" if description
|
68
40
|
|
69
41
|
banner
|
70
42
|
end
|
71
43
|
|
72
|
-
def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
73
|
-
nl = "\n"
|
74
|
-
blk ||= proc { |l| to << (l.index(nl, -1) ? l : l + nl) }
|
75
|
-
|
76
|
-
no_opt = @arg_stack.flatten(1).empty? && no_options
|
77
|
-
blk.call("\nOptions:") if to.is_a?(String) && !no_opt
|
78
|
-
|
79
|
-
res = super(to, width, max, indent, &blk)
|
80
|
-
@arg_stack.flatten(1).each do |sw|
|
81
|
-
sw.summarize({}, {}, width, max, indent, &blk)
|
82
|
-
end
|
83
|
-
|
84
|
-
unless @commands.empty?
|
85
|
-
blk.call("\nCommands:") if to.is_a?(String)
|
86
|
-
@commands.each do |name, command|
|
87
|
-
sw = Switch::NoArgument.new(nil, nil, [name], nil, nil, command[1] ? [command[1]] : [], nil)
|
88
|
-
sw.summarize({}, {}, width, max, indent, &blk)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
res
|
93
|
-
end
|
94
|
-
|
95
|
-
def define_opt_switch_values(target, swvs)
|
96
|
-
case target
|
97
|
-
when :tail
|
98
|
-
base.append(*swvs)
|
99
|
-
when :head
|
100
|
-
top.prepend(*swvs)
|
101
|
-
else
|
102
|
-
top.append(*swvs)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
private :define_opt_switch_values
|
106
|
-
|
107
44
|
def search_arg_switch_atype(sw0)
|
108
|
-
|
45
|
+
visit(:tap) do |el|
|
109
46
|
el.atype.each do |klass, atype|
|
110
47
|
next unless atype[1] == sw0.conv
|
111
48
|
return [nil, nil] if klass == Object
|
@@ -118,150 +55,130 @@ class XOptionParser < ::OptionParser
|
|
118
55
|
end
|
119
56
|
private :search_arg_switch_atype
|
120
57
|
|
121
|
-
def fix_arg_switch(sw0)
|
58
|
+
def fix_arg_switch(sw0)
|
122
59
|
pattern, conv = search_arg_switch_atype(sw0)
|
123
|
-
|
124
|
-
sw0.instance_variable_set(:@conv, conv)
|
125
|
-
sw0.instance_variable_set(:@short, [sw0.desc.shift])
|
126
|
-
|
127
|
-
# arg pattern example:
|
128
|
-
# * req req => 1..1, 1..1
|
129
|
-
# * req [opt] => 1..1, 0..1
|
130
|
-
# * req... => 1..nil
|
131
|
-
# * [opt...] => 0..nil
|
132
|
-
# * req [opt] req... => 1..1, 0..1, 1..nil
|
133
|
-
# * req [opt...] => 1..1, 0..nil
|
134
|
-
# * req [opt...] req => 1..1, 0..nil, 1..1
|
135
|
-
ranges = sw0.short.first.scan(/(?:\[\s*(.*?)\s*\]|(\S+))/).map do |opt, req|
|
136
|
-
(opt ? 0 : 1)..((opt || req).end_with?('...') ? nil : 1)
|
137
|
-
end
|
138
|
-
unless self.class.valid_arg_switch_ranges?(ranges)
|
139
|
-
raise ArgumentError, "unsupported argument format: #{sw0.short.first.inspect}"
|
140
|
-
end
|
141
|
-
|
142
|
-
sw0.instance_variable_set(:@ranges, ranges)
|
143
|
-
sw0.define_singleton_method(:ranges) { @ranges }
|
144
|
-
sw0
|
60
|
+
Switch::SimpleArgument.new(pattern, conv, nil, nil, sw0.desc[0], sw0.desc[1..], sw0.block)
|
145
61
|
end
|
146
62
|
private :fix_arg_switch
|
147
63
|
|
148
|
-
def
|
149
|
-
|
150
|
-
unless self.class.valid_arg_switch_ranges?(ranges)
|
151
|
-
raise ArgumentError, "unsupported argument format: #{sw0.short.first.inspect}"
|
152
|
-
end
|
153
|
-
|
154
|
-
sw0
|
155
|
-
end
|
156
|
-
private :valid_arg_switch
|
157
|
-
|
158
|
-
def define_arg_switch(target, sw0)
|
159
|
-
case target
|
160
|
-
when :tail
|
161
|
-
@arg_stack[1].append(sw0)
|
162
|
-
when :head
|
163
|
-
@arg_stack[0].prepend(sw0)
|
164
|
-
else
|
165
|
-
@arg_stack[0].append(sw0)
|
166
|
-
end
|
167
|
-
|
168
|
-
valid_arg_switch(sw0)
|
169
|
-
end
|
170
|
-
private :define_arg_switch
|
171
|
-
|
172
|
-
def define_at(target, *opts, &block)
|
173
|
-
sw = make_switch(opts, block || proc {})
|
64
|
+
def make_switch(opts, block = nil)
|
65
|
+
sw = super(opts, block || proc {})
|
174
66
|
sw0 = sw[0]
|
175
|
-
if sw0.short || sw0.long
|
176
|
-
define_opt_switch_values(target, sw)
|
177
|
-
else
|
178
|
-
sw0 = fix_arg_switch(sw0)
|
179
|
-
define_arg_switch(target, sw0)
|
180
|
-
end
|
181
|
-
sw0
|
182
|
-
end
|
183
|
-
private :define_at
|
184
|
-
|
185
|
-
def define(*args, &block)
|
186
|
-
define_at(:body, *args, &block)
|
187
|
-
end
|
188
|
-
alias def_option define
|
189
|
-
|
190
|
-
def define_head(*args, &block)
|
191
|
-
define_at(:head, *args, &block)
|
192
|
-
end
|
193
|
-
alias def_head_option define_head
|
67
|
+
return sw if sw0.short || sw0.long
|
194
68
|
|
195
|
-
|
196
|
-
|
69
|
+
sw0 = fix_arg_switch(sw0)
|
70
|
+
long = sw0.arg.scan(/(?:\[\s*(.*?)\s*\]|(\S+))/).flatten.compact
|
71
|
+
[sw0, nil, long]
|
197
72
|
end
|
198
|
-
alias def_tail_option define_tail
|
199
|
-
|
200
|
-
def parse_arguments(original_argv) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
201
|
-
arg_sws = @arg_stack.flatten(1)
|
202
|
-
return original_argv if arg_sws.empty?
|
203
|
-
|
204
|
-
req_count, opt_count, rest_req_count, last_req_count =
|
205
|
-
self.class.calc_arg_switch_ranges_counts(arg_sws.map(&:ranges).flatten(1))
|
206
73
|
|
207
|
-
|
208
|
-
|
74
|
+
def parse_arguments(argv) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
75
|
+
arg_sws = select { |sw| sw.is_a?(Switch::SimpleArgument) }
|
76
|
+
return argv if arg_sws.empty?
|
209
77
|
|
210
|
-
|
211
|
-
|
78
|
+
sws_ranges = arg_sws.map(&:ranges).flatten(1)
|
79
|
+
req_count = sws_ranges.sum(&:begin)
|
80
|
+
raise MissingArgument, argv.join(' ') if argv.size < req_count
|
212
81
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
rest_argv = argv[(req_count + opt_size)...(argv.size - last_req_count)]
|
82
|
+
opt_count = sws_ranges.sum(&:size) - sws_ranges.size
|
83
|
+
opt_index = argv[req_count...].index { |arg| @commands.include?(arg) } unless @commands.empty?
|
84
|
+
opt_count = [opt_count, opt_index || Float::INFINITY, argv.size - req_count].min
|
217
85
|
|
218
86
|
arg_sws.each do |sw|
|
219
87
|
conv = proc { |v| sw.send(:conv_arg, *sw.send(:parse_arg, v))[2] }
|
220
88
|
a = sw.ranges.map do |r|
|
221
89
|
if r.end.nil?
|
222
|
-
|
90
|
+
rest_size = r.begin + opt_count
|
91
|
+
req_count -= r.begin
|
92
|
+
opt_count = 0
|
93
|
+
argv.slice!(0...rest_size).map(&conv)
|
223
94
|
elsif r.begin.zero?
|
224
|
-
conv.call(
|
95
|
+
next conv.call(nil) if opt_count.zero?
|
96
|
+
|
97
|
+
opt_count -= 1
|
98
|
+
conv.call(argv.shift)
|
225
99
|
else
|
226
|
-
|
100
|
+
req_count -= 1
|
101
|
+
conv.call(argv.shift)
|
227
102
|
end
|
228
103
|
end
|
229
104
|
sw.block.call(*a)
|
230
105
|
end
|
231
106
|
|
232
|
-
|
107
|
+
argv
|
233
108
|
end
|
234
109
|
private :parse_arguments
|
235
110
|
|
236
|
-
def
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
111
|
+
def parse_in_order(*args, &nonopt)
|
112
|
+
argv = []
|
113
|
+
rest = if nonopt
|
114
|
+
super(*args, &argv.method(:<<))
|
115
|
+
else
|
116
|
+
argv = super(*args)
|
117
|
+
end
|
118
|
+
parse_arguments(argv).map(&nonopt)
|
119
|
+
rest
|
120
|
+
end
|
121
|
+
private :parse_in_order
|
122
|
+
|
123
|
+
def order!(*args, **kwargs)
|
124
|
+
return super(*args, **kwargs) if @commands.empty?
|
241
125
|
|
242
|
-
argv = super(*args, **kwargs
|
126
|
+
argv = super(*args, **kwargs, &nil)
|
243
127
|
return argv if argv.empty?
|
244
128
|
|
245
129
|
name = argv.shift
|
246
|
-
|
247
|
-
return
|
130
|
+
sw = @commands[name]
|
131
|
+
return sw.block.call.send(block_given? ? :permute! : :order!, *args, **kwargs) if sw
|
248
132
|
|
249
133
|
puts "#{program_name}:" \
|
250
134
|
"'#{name}' is not a #{program_name} command. See '#{program_name} --help'."
|
251
135
|
exit
|
252
136
|
end
|
253
137
|
|
254
|
-
def permute!(*args, **kwargs)
|
255
|
-
parse_arguments(super(*args, **kwargs))
|
256
|
-
end
|
257
|
-
|
258
138
|
def command(name, desc = nil, *args, &block)
|
259
|
-
|
139
|
+
sw0 = Switch::SummarizeArgument.new(nil, nil, nil, nil, name.to_s, desc ? [desc] : [], nil) do
|
260
140
|
self.class.new(desc, *args) do |opt|
|
261
141
|
opt.program_name = "#{program_name} #{name}"
|
262
|
-
block
|
142
|
+
block&.call(opt)
|
263
143
|
end
|
264
|
-
end
|
144
|
+
end
|
145
|
+
top.append(sw0, nil, [sw0.arg])
|
146
|
+
@commands[name.to_s] = sw0
|
265
147
|
nil
|
266
148
|
end
|
149
|
+
|
150
|
+
class Switch < ::OptionParser::Switch
|
151
|
+
class SummarizeArgument < NoArgument
|
152
|
+
undef_method :add_banner
|
153
|
+
|
154
|
+
def summarize(*args)
|
155
|
+
original_arg = arg
|
156
|
+
@short = arg.scan(/\[\s*.*?\s*\]|\S+/)
|
157
|
+
@arg = nil
|
158
|
+
res = super(*args)
|
159
|
+
@arg = original_arg
|
160
|
+
@short = nil
|
161
|
+
res
|
162
|
+
end
|
163
|
+
|
164
|
+
def match_nonswitch?(*args)
|
165
|
+
super(*args) if @pattern.is_a?(Regexp)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class SimpleArgument < SummarizeArgument
|
170
|
+
attr_reader :ranges
|
171
|
+
|
172
|
+
def initialize(*args)
|
173
|
+
super(*args)
|
174
|
+
@ranges = arg.scan(/\[\s*(.*?)\s*\]|(\S+)/).map do |opt, req|
|
175
|
+
(opt ? 0 : 1)..((opt || req).end_with?('...') ? nil : 1)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_banner(to)
|
180
|
+
to << " #{arg}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
267
184
|
end
|
data/lib/xoptparse/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xoptparse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ofk
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|