xoptparse 0.1.0 → 0.2.0

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: 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