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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4581fc5af988d4643c8cadc47f9d685c11bfc93c9c7e467136eb521d4926338
4
- data.tar.gz: 7a126c2f9f468d61fc7bc72cdc62d6694f78b8505c0d937a54c9731340c6b5cb
3
+ metadata.gz: ed8ba0e138b8c1d6e68ca65d8e022ef51f3456647121207904074ffa8d53ac9f
4
+ data.tar.gz: e3ffc6e2caa748fadeb8b9a6f3129491d2997c8edf89780689bc6eac02c07489
5
5
  SHA512:
6
- metadata.gz: b3e8e688e2e5499d38dbecd687526d9b8fd9b0f47ce2b2dd2244518f8bd0917a8dfb22a4f49e02b42de7e8bc613d9b8add4dbd77738491b48ae792fb82bfa655
7
- data.tar.gz: 12a596b13d62628cca1b31fcb92eef894673a0bb2487dd2624075ae498fe597f3d62e8094cce5149b91a02a3b12036aabf107c5d69eb408f2e3e5d1344a7892e
6
+ metadata.gz: f160dfefb34a43a2b9f60ec775679e810613665edba3138857836467285eb5d8ce58c664687bd823322c619ea32a8c84ddab328b7e6a432c5f784dfffdff9727
7
+ data.tar.gz: '0950deea41a448199bc90aa1fb65365940fee26c3e3dd994c27d62e8e5677aaaf160e2234a91e24539ae36ecbc498f17aded45674e50e694c1ce4599c45ed1d5'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- xoptparse (0.1.0)
4
+ xoptparse (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -4,108 +4,45 @@ require 'xoptparse/version'
4
4
  require 'optparse'
5
5
 
6
6
  class XOptionParser < ::OptionParser
7
- class << self
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
- visit(:summarize, {}, {}) { return false }
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 # rubocop:disable Metrics/AbcSize
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
- @stack.reverse_each do |el|
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) # rubocop:disable Metrics/AbcSize
58
+ def fix_arg_switch(sw0)
122
59
  pattern, conv = search_arg_switch_atype(sw0)
123
- sw0.instance_variable_set(:@pattern, pattern)
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 valid_arg_switch(sw0)
149
- ranges = @arg_stack.flatten(1).map(&:ranges).flatten(1)
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
- def define_tail(*args, &block)
196
- define_at(:tail, *args, &block)
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
- argv_min = req_count + last_req_count + (rest_req_count || 0)
208
- raise MissingArgument, original_argv.join(' ').to_s if original_argv.size < argv_min
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
- argv_max = rest_req_count ? nil : argv_min + opt_count
211
- argv = original_argv.slice!(0...argv_max)
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
- opt_size = [argv.size - argv_min, opt_count].min
214
- req_argv = argv[0...req_count] + argv[(argv.size - last_req_count)...argv.size]
215
- opt_argv = argv[req_count...(req_count + opt_size)] # + ([nil] * (opt_count - opt_size))
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
- rest_argv.map(&conv)
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(opt_argv.shift)
95
+ next conv.call(nil) if opt_count.zero?
96
+
97
+ opt_count -= 1
98
+ conv.call(argv.shift)
225
99
  else
226
- conv.call(req_argv.shift)
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
- original_argv
107
+ argv
233
108
  end
234
109
  private :parse_arguments
235
110
 
236
- def order!(*args, **kwargs) # rubocop:disable Metrics/AbcSize
237
- if @commands.empty?
238
- argv = super(*args, **kwargs)
239
- return block_given? ? argv : parse_arguments(argv)
240
- end
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) { |a| throw :terminate, a }
126
+ argv = super(*args, **kwargs, &nil)
243
127
  return argv if argv.empty?
244
128
 
245
129
  name = argv.shift
246
- cmd = @commands[name]
247
- return cmd.first.call.send(block_given? ? :permute! : :order!, *args, **kwargs) if cmd
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
- @commands[name.to_s] = [proc do
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.call(opt) if block
142
+ block&.call(opt)
263
143
  end
264
- end, desc]
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
@@ -3,5 +3,5 @@
3
3
  require 'optparse'
4
4
 
5
5
  class XOptionParser < ::OptionParser
6
- VERSION = '0.1.0'
6
+ VERSION = '0.2.0'
7
7
  end
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.1.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 00:00:00.000000000 Z
11
+ date: 2020-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler