xoptparse 0.1.0 → 0.6.1

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: b67b857f7260f74d4f14c343279a3bb75002061470b49473206c95ad5cd38816
4
+ data.tar.gz: 3d7056c4e16be3b0f6b71cb85cacc39481ee9523ce427840dc960126c3ddfbba
5
5
  SHA512:
6
- metadata.gz: b3e8e688e2e5499d38dbecd687526d9b8fd9b0f47ce2b2dd2244518f8bd0917a8dfb22a4f49e02b42de7e8bc613d9b8add4dbd77738491b48ae792fb82bfa655
7
- data.tar.gz: 12a596b13d62628cca1b31fcb92eef894673a0bb2487dd2624075ae498fe597f3d62e8094cce5149b91a02a3b12036aabf107c5d69eb408f2e3e5d1344a7892e
6
+ metadata.gz: a6c00977cccfead1e09cdde9b40592a7a5ef23777c516fbd65332f2a08a50a42f94bf669c4bc89f42e5c84ed427fe61dae5544cc2c33e3c0e8db3f4147b0505c
7
+ data.tar.gz: 3f63f73775990580eb2496a7b99975d94f7d5f773acc3ae4c2cdaae1acaa5930cd24212d555ed1f59229853e2d47f0d8c7bd10368351c306ca21c0e3e9fac8b1
@@ -12,6 +12,9 @@ Metrics/AbcSize:
12
12
  Exclude:
13
13
  - test/**/*
14
14
 
15
+ Metrics/BlockLength:
16
+ Enabled: false
17
+
15
18
  Metrics/ClassLength:
16
19
  Enabled: false
17
20
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- xoptparse (0.1.0)
4
+ xoptparse (0.6.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -52,7 +52,7 @@ DEPENDENCIES
52
52
  minitest-power_assert
53
53
  minitest-reporters
54
54
  rake (~> 12.3.3)
55
- rubocop (= 0.84.0)
55
+ rubocop
56
56
  simplecov
57
57
  xoptparse!
58
58
 
@@ -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
49
19
  end
50
20
 
21
+ def select(*args, &block)
22
+ Enumerator.new do |y|
23
+ visit(:each_option) { |el| y << el }
24
+ end.select(*args, &block)
25
+ end
26
+ private :select
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
@@ -119,149 +56,200 @@ class XOptionParser < ::OptionParser
119
56
  private :search_arg_switch_atype
120
57
 
121
58
  def fix_arg_switch(sw0) # rubocop:disable Metrics/AbcSize
122
- 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}"
59
+ if !(sw0.short || sw0.long)
60
+ pattern, conv = search_arg_switch_atype(sw0)
61
+ Switch::SimpleArgument.new(pattern, conv, nil, nil, sw0.desc[0], sw0.desc[1..], sw0.block)
62
+ elsif sw0.is_a?(Switch::PlacedArgument) && sw0.long.size == 1 && /^--\[no-\]/ =~ sw0.long.first
63
+ args = [sw0.pattern, sw0.conv, sw0.short, sw0.long, sw0.arg, sw0.desc, sw0.block]
64
+ Switch::FlagArgument.new(*args)
65
+ else
66
+ sw0
140
67
  end
141
-
142
- sw0.instance_variable_set(:@ranges, ranges)
143
- sw0.define_singleton_method(:ranges) { @ranges }
144
- sw0
145
68
  end
146
69
  private :fix_arg_switch
147
70
 
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
71
+ def make_switch(opts, block = nil)
72
+ sw = super(opts, block || proc {})
73
+ sw0 = sw[0] = fix_arg_switch(sw[0])
74
+ return sw if sw0.short || sw0.long
153
75
 
154
- sw0
76
+ long = sw0.arg_parameters.map(&:first)
77
+ [sw0, nil, long]
155
78
  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)
79
+
80
+ def parse_arguments(argv, setter = nil, opts = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
81
+ arg_sws = select { |sw| sw.is_a?(Switch::SummarizeArgument) && !opts.include?(sw.switch_name) }
82
+ return argv if arg_sws.empty?
83
+
84
+ sws_ranges = arg_sws.map(&:ranges).flatten(1)
85
+ req_count = sws_ranges.sum(&:begin)
86
+ opt_count = sws_ranges.sum(&:size) - sws_ranges.size
87
+ opt_index = argv[req_count...].index { |arg| @commands.include?(arg) } unless @commands.empty?
88
+ opt_count = [opt_count, opt_index || Float::INFINITY, argv.size - req_count].min
89
+
90
+ arg_sws.each_with_index do |sw, i|
91
+ if sw.is_a?(Switch::SimpleArgument)
92
+ callable = false
93
+ conv = proc do |v|
94
+ callable = true
95
+ sw.send(:conv_arg, *sw.send(:parse_arg, v))[2]
96
+ end
97
+ a = sw.ranges.map do |r|
98
+ raise MissingArgument if r.begin.positive? && argv.empty?
99
+
100
+ if r.end.nil?
101
+ rest_size = r.begin + opt_count
102
+ req_count -= r.begin
103
+ opt_count = 0
104
+ argv.slice!(0...rest_size).map(&conv)
105
+ elsif r.begin.positive?
106
+ req_count -= 1
107
+ conv.call(argv.shift)
108
+ elsif opt_count.positive?
109
+ opt_count -= 1
110
+ conv.call(argv.shift)
111
+ end
112
+ end
113
+ if callable
114
+ val = sw.block.call(*a)
115
+ setter&.call(sw.switch_name, val)
116
+ end
117
+ elsif sw.pattern =~ argv.first
118
+ argv.shift
119
+ @command_switch = sw
120
+ break
121
+ elsif !argv.empty? && i == arg_sws.size - 1
122
+ raise MissingArgument
123
+ end
166
124
  end
167
125
 
168
- valid_arg_switch(sw0)
126
+ argv
169
127
  end
170
- private :define_arg_switch
128
+ private :parse_arguments
171
129
 
172
- def define_at(target, *opts, &block)
173
- sw = make_switch(opts, block || proc {})
174
- 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)
130
+ def parse_in_order(argv = default_argv, setter = nil, &nonopt)
131
+ nonopts = []
132
+ opts = {}
133
+ opts_setter = proc do |name, val|
134
+ opts[name] = true
135
+ setter&.call(name, val)
180
136
  end
181
- sw0
137
+ rest = if nonopt
138
+ super(argv, opts_setter, &nonopts.method(:<<))
139
+ else
140
+ nonopts = super(argv, opts_setter)
141
+ end
142
+ parse_arguments(nonopts, setter, opts).map(&nonopt)
143
+ rest
182
144
  end
183
- private :define_at
145
+ private :parse_in_order
184
146
 
185
- def define(*args, &block)
186
- define_at(:body, *args, &block)
187
- end
188
- alias def_option define
147
+ def order!(*args, into: nil, **kwargs)
148
+ return super(*args, into: into, **kwargs) if @commands.empty?
189
149
 
190
- def define_head(*args, &block)
191
- define_at(:head, *args, &block)
192
- end
193
- alias def_head_option define_head
150
+ @command_switch = nil
151
+ argv = super(*args, into: into, **kwargs, &nil)
152
+ return argv unless @command_switch
194
153
 
195
- def define_tail(*args, &block)
196
- define_at(:tail, *args, &block)
154
+ into = into[@command_switch.arg.to_sym] = {} if into
155
+ @command_switch.block.call.send(block_given? ? :permute! : :order!, *args, into: into, **kwargs)
197
156
  end
198
- alias def_tail_option define_tail
199
157
 
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))
158
+ def command(name, desc = nil, *args, &block)
159
+ name = name.to_s
160
+ pattern = /^#{name.gsub('_', '[-_]?')}$/i
161
+ sw0 = Switch::SummarizeArgument.new(pattern, nil, nil, nil, name, desc ? [desc] : [], nil) do
162
+ self.class.new(desc, *args) do |opt|
163
+ opt.program_name = "#{program_name} #{name}"
164
+ block&.call(opt)
165
+ end
166
+ end
167
+ top.append(sw0, nil, [sw0.arg])
168
+ @commands[name] = sw0
169
+ nil
170
+ end
206
171
 
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
172
+ class Switch < ::OptionParser::Switch
173
+ class SummarizeArgument < self
174
+ undef_method :add_banner
209
175
 
210
- argv_max = rest_req_count ? nil : argv_min + opt_count
211
- argv = original_argv.slice!(0...argv_max)
176
+ attr_reader :ranges
177
+ attr_reader :arg_parameters
212
178
 
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)]
179
+ def initialize(*)
180
+ super
181
+ @ranges = []
182
+ @arg_parameters = arg.scan(/\[\s*(.*?)\s*\]|(\S+)/).map do |opt, req|
183
+ name = opt || req
184
+ [name.sub(/\s*\.\.\.$/, ''), opt ? :opt : :req, name.end_with?('...') ? :rest : nil]
185
+ end
186
+ end
217
187
 
218
- arg_sws.each do |sw|
219
- conv = proc { |v| sw.send(:conv_arg, *sw.send(:parse_arg, v))[2] }
220
- a = sw.ranges.map do |r|
221
- if r.end.nil?
222
- rest_argv.map(&conv)
223
- elsif r.begin.zero?
224
- conv.call(opt_argv.shift)
225
- else
226
- conv.call(req_argv.shift)
188
+ def summarize(*)
189
+ original_arg = arg
190
+ @short = arg_parameters.map do |name, type, rest|
191
+ var = "#{name}#{rest ? '...' : ''}"
192
+ type == :req ? var : "[#{var}]"
227
193
  end
194
+ @arg = nil
195
+ res = super
196
+ @arg = original_arg
197
+ @short = nil
198
+ res
228
199
  end
229
- sw.block.call(*a)
230
- end
231
200
 
232
- original_argv
233
- end
234
- private :parse_arguments
201
+ def match_nonswitch?(*)
202
+ super if @pattern.is_a?(Regexp)
203
+ end
235
204
 
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
205
+ def switch_name
206
+ arg_parameters.first.first
207
+ end
241
208
 
242
- argv = super(*args, **kwargs) { |a| throw :terminate, a }
243
- return argv if argv.empty?
209
+ def parse(_arg, _argv)
210
+ raise XOptionParser::InvalidOption
211
+ end
212
+ end
244
213
 
245
- name = argv.shift
246
- cmd = @commands[name]
247
- return cmd.first.call.send(block_given? ? :permute! : :order!, *args, **kwargs) if cmd
214
+ class SimpleArgument < SummarizeArgument
215
+ def initialize(*)
216
+ super
217
+ @ranges = arg_parameters.map do |_name, type, rest|
218
+ (type == :req ? 1 : 0)..(rest == :rest ? nil : 1)
219
+ end
220
+ end
248
221
 
249
- puts "#{program_name}:" \
250
- "'#{name}' is not a #{program_name} command. See '#{program_name} --help'."
251
- exit
252
- end
222
+ def add_banner(to)
223
+ to << " #{arg}"
224
+ end
253
225
 
254
- def permute!(*args, **kwargs)
255
- parse_arguments(super(*args, **kwargs))
256
- end
226
+ def parse(arg, argv) # rubocop:disable Metrics/CyclomaticComplexity
227
+ case ranges.size
228
+ when 0
229
+ yield(NeedlessArgument, arg) if arg
230
+ conv_arg(arg)
231
+ when 1
232
+ unless arg
233
+ raise XOptionParser::MissingArgument if argv.empty?
234
+
235
+ arg = argv.shift
236
+ end
237
+ arg = [arg] if ranges.first.end.nil?
238
+ conv_arg(*parse_arg(arg, &method(:raise)))
239
+ else
240
+ super(arg, argv)
241
+ end
242
+ end
243
+ end
257
244
 
258
- def command(name, desc = nil, *args, &block)
259
- @commands[name.to_s] = [proc do
260
- self.class.new(desc, *args) do |opt|
261
- opt.program_name = "#{program_name} #{name}"
262
- block.call(opt) if block
245
+ class FlagArgument < PlacedArgument
246
+ def parse(arg, argv, &error)
247
+ super(arg, argv, &error).tap do |val|
248
+ raise OptionParser::InvalidArgument if val[0].nil? && val[2].nil?
249
+ end
250
+ rescue OptionParser::InvalidArgument
251
+ conv_arg(arg)
263
252
  end
264
- end, desc]
265
- nil
253
+ end
266
254
  end
267
255
  end
@@ -3,5 +3,5 @@
3
3
  require 'optparse'
4
4
 
5
5
  class XOptionParser < ::OptionParser
6
- VERSION = '0.1.0'
6
+ VERSION = '0.6.1'
7
7
  end
@@ -29,6 +29,6 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'minitest-power_assert'
30
30
  spec.add_development_dependency 'minitest-reporters'
31
31
  spec.add_development_dependency 'rake', '~> 12.3.3'
32
- spec.add_development_dependency 'rubocop', '0.84.0'
32
+ spec.add_development_dependency 'rubocop'
33
33
  spec.add_development_dependency 'simplecov'
34
34
  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.6.1
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-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,16 +84,16 @@ dependencies:
84
84
  name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: 0.84.0
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: 0.84.0
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: simplecov
99
99
  requirement: !ruby/object:Gem::Requirement