xoptparse 0.1.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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