optbind 0.4.0 → 0.5.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
  SHA1:
3
- metadata.gz: a75ae614f40e93666809a3bcefac9e58ed61e699
4
- data.tar.gz: d3a138e3f55237c587aa1d1901f529c3415afa7a
3
+ metadata.gz: 460ca3303bb62d3d9082fad5a691d3c96d0c68a7
4
+ data.tar.gz: 279ef0028459cb0b10e90f4b25421d5894cb19eb
5
5
  SHA512:
6
- metadata.gz: c487f3899ff91d0a18a25647ffe10ae32dcf2cc0bf3b88aa524bb993bea749b271623b3be965970e56169bf5bd87afcc384bbf67c7f3b2b31be59bf64d45eff0
7
- data.tar.gz: 2f52caa014b1fb9b66a228e1c557ba3daef0b3d220a392671880a46207fbcc71675e8184d34e2a25a7f88384e0faf88e5109baf6d4f0f584c2e7655ea026f31f
6
+ metadata.gz: f0ea5b17c67992e22e74486b4260536d4e298e9fff683e3c367eb718a91ff78f01f3aa19c9c1f4f038d1af58cfa7d34dece47467d467a946a4e404cd603255fd
7
+ data.tar.gz: d42a36c32091092fce179c4d480714e58f14ec45143a3ffd10dc86a4490b450ffdd0a5650ff01e07191dea701c6d13c11e95c8db82750ddb0b598d7db122c0c2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 0.4.0
2
+
3
+ * Support binding options to class variables
4
+
5
+ * Support argument patterns and types, make argument processing in fact same as option processing
6
+
7
+ * Enable setting program name via constant and set both program name and version for both default and supplied parsers
8
+
9
+ * Enable non-destructive parsing for ARGV
10
+
11
+ * Raise an error on parsing failure instead of totally aborting execution
12
+
13
+ * Remove `order` and `permute` methods
14
+
15
+ * Conform to Ruby 2.3 and frozen strings
16
+
17
+ *Pavol Zbell*
18
+
1
19
  ## 0.3.2
2
20
 
3
21
  * Fix handling of blanks as array arguments
data/README.md CHANGED
@@ -3,12 +3,12 @@
3
3
  [![Build Status](https://img.shields.io/travis/pavolzbell/optbind.svg)](https://travis-ci.org/pavolzbell/optbind)
4
4
  [![Gem Version](https://img.shields.io/gem/v/optbind.svg)](https://rubygems.org/gems/optbind)
5
5
 
6
- Binds command-line options to variables.
6
+ Binds command-line options to objects or variables.
7
7
 
8
8
  Extends command-line option analysis by wrapping an instance of standard [`OptionParser`](http://ruby-doc.org/stdlib-2.3.1/libdoc/optparse/rdoc/OptionParser.html).
9
- Enables binding of options and arguments to instance or local variables. Provides `Hash` and `String` only interfaces
10
- to define command line options, unlike a mixed interface by standard library. Supports access to default values and
11
- partial argument analysis. Builds Git-like options and help by default.
9
+ Enables binding of options and arguments to `Hash` entries, public accessors, local, instance, or class variables. Provides `Hash` and `String` only interfaces
10
+ to define command-line options, unlike a mixed interface by standard library. Supports access to default values and argument analysis. Builds Git-like options
11
+ and help by default.
12
12
 
13
13
  ## Installation
14
14
 
@@ -62,11 +62,47 @@ Note that plain `OptionBinder.new` binds to local variables of top level binding
62
62
 
63
63
  See specs for more examples and details on usage.
64
64
 
65
+ ### Definitions
66
+
67
+ Option and argument definitions include:
68
+
69
+ #### Hash options
70
+
71
+ ```ruby
72
+ e, t, v, b, m = false, 80, true, 'master', [STDIN]
73
+
74
+ OptionBinder.new do |b|
75
+ b.usage %w([<options>] <branch> [<path>...])
76
+ b.usage %w([<options>] -e <branch> [<command>...])
77
+ b.option variable: :e, names: %w(e eval)
78
+ b.option variable: :t, mode: :OPTIONAL, short: 't', long: 'trim', argument: '[=<length>]', type: Integer
79
+ b.option variable: :v, names: %w(-v --[no]-verbose), description: 'Be more verbose.'
80
+ b.argument variable: :b, mode: :REQUIRED, argument: '<branch>'
81
+ b.argument variable: :a, mode: :OPTIONAL, multiple: true, argument: '[<mix>...]'
82
+ end
83
+ ```
84
+
85
+ #### Plain strings
86
+
87
+ ```ruby
88
+ e, t, v, b, m = false, 80, true, 'master', [STDIN]
89
+
90
+ OptionBinder.new do |b|
91
+ b.usage '[<options>] <branch> [<path>...]'
92
+ b.usage '[<options>] -e <branch> [<command>...]'
93
+ b.option 'e -e --eval'
94
+ b.option 't -t --trim[=<length:integer>]'
95
+ b.option 'v -v --[no-]verbose Be more verbose.'
96
+ b.argument 'b <branch>'
97
+ b.argument 'm [<mix>...]'
98
+ end
99
+ ```
100
+
65
101
  ### Bindings
66
102
 
67
103
  Various binding possibilities include:
68
104
 
69
- #### Bind to `Hash` object
105
+ #### Bind to `Hash` entries
70
106
 
71
107
  Create target:
72
108
 
@@ -77,7 +113,7 @@ options = { input: STDIN, output: STDOUT }
77
113
  Use `OptionBinder` directly:
78
114
 
79
115
  ```ruby
80
- OptionBinder.new(target: options) do
116
+ OptionBinder.new(target: options) do |b|
81
117
  # ...
82
118
  end
83
119
  ```
@@ -107,7 +143,7 @@ end
107
143
  Use `OptionBinder` directly:
108
144
 
109
145
  ```ruby
110
- OptionBinder.new(target: options) do
146
+ OptionBinder.new(target: options) do |b|
111
147
  # ...
112
148
  end
113
149
  ```
@@ -135,7 +171,7 @@ options = Options.new
135
171
  Use `OptionBinder` directly:
136
172
 
137
173
  ```ruby
138
- OptionBinder.new(target: options, bind: :to_class_variables) do
174
+ OptionBinder.new(target: options, bind: :to_class_variables) do |b|
139
175
  # ...
140
176
  end
141
177
  ```
@@ -165,7 +201,7 @@ options = Options.new
165
201
  Use `OptionBinder` directly:
166
202
 
167
203
  ```ruby
168
- OptionBinder.new(target: options, bind: :to_instance_variables) do
204
+ OptionBinder.new(target: options, bind: :to_instance_variables) do |b|
169
205
  # ...
170
206
  end
171
207
  ```
@@ -189,7 +225,7 @@ input, output = STDIN, STDOUT
189
225
  Use `OptionBinder` directly:
190
226
 
191
227
  ```ruby
192
- OptionBinder.new(target: TOPLEVEL_BINDING, bind: :to_local_variables) do
228
+ OptionBinder.new(target: TOPLEVEL_BINDING, bind: :to_local_variables) do |b|
193
229
  # ...
194
230
  end
195
231
  ```
@@ -197,7 +233,7 @@ end
197
233
  Use `OptionBinder` directly with top level binding object as target by default:
198
234
 
199
235
  ```ruby
200
- OptionBinder.new do
236
+ OptionBinder.new do |b|
201
237
  # ...
202
238
  end
203
239
  ```
@@ -218,6 +254,58 @@ ARGV.define_and_bind(to: :locals) do
218
254
  end
219
255
  ```
220
256
 
257
+ ### Extensions
258
+
259
+ Several available extensions include:
260
+
261
+ #### Default
262
+
263
+ Adds default values to option descriptions.
264
+
265
+ ```ruby
266
+ require 'optbind/default'
267
+
268
+
269
+ ```
270
+
271
+ #### Mode
272
+
273
+ Adds `order` and `permute` methods.
274
+
275
+ ```ruby
276
+ require 'optbind/mode'
277
+
278
+ ARGV.order
279
+ ```
280
+
281
+ Note that `order!` and `permute!` methods in `ARGV` from `OptionParser` are redefined to raise an unsupported error without this extension.
282
+
283
+ #### Type
284
+
285
+ Adds various common types to accept in definitions.
286
+
287
+ ```ruby
288
+ require 'optbind/type'
289
+
290
+ ...
291
+
292
+ binder.option 'm --matcher=<pattern:Regexp>'
293
+ binder.option 'r --repository=<uri:URI>'
294
+ ```
295
+
296
+ #### Handler
297
+
298
+ Adds various common handlers to accept in definitions.
299
+
300
+ ```ruby
301
+ require 'optbind/handler'
302
+
303
+ ...
304
+
305
+ binder.option 's --storage=<name>', &included_in(%w(file memory))
306
+ binder.option 'a --attachments=<ids>', &listed_as(Integer)
307
+ ```
308
+
221
309
  ## Testing
222
310
 
223
311
  bundle exec rspec
data/TODO.md CHANGED
@@ -1,15 +1,13 @@
1
1
  # TODOS
2
2
 
3
- - move order and permute to separate package, required on demand
4
-
5
- - `Switch#parser_opts_from_string` - support regexps with options, like `=<name:/[a-z]/i>`
6
- - `Switch#parser_opts_from_string` - support ranges, like `=<indent:0..8>`
7
-
8
- - add custom type definitions to separate package, required on demand
3
+ - test against special cases for multiple args like 'a <ids...>' with '--x 1'
4
+ - add optbind/defaults from extise as separate package, required on demand
5
+
6
+ - add support for arrays of int -> <x:Array:Integer> and <x:Integer>...
9
7
  - add support for ranges on `OptionParser` level
10
8
 
11
- - add optbind/handlers from extise as separate package, required on demand
12
- - add optbind/defaults from extise as separate package, required on demand
9
+ - `Switch#parser_opts_from_string` - support regexps with options, like `=<name:/[a-z]/i>`
10
+ - `Switch#parser_opts_from_string` - support ranges, like `=<indent:0..8>`
13
11
 
14
12
  - make optional extensions for `OptionParser` monkey-patching `make_switch` to support hash-or-string-exclusive arguments
15
13
 
@@ -0,0 +1,5 @@
1
+ module OptionBinder::Default
2
+ # TODO
3
+ end
4
+
5
+ OptionBinder.prepend OptionBinder::Default
@@ -0,0 +1,29 @@
1
+ module OptionBinder::Handler
2
+ def matched_by(p)
3
+ -> (v) { p =~ v.to_s ? v : raise(OptionParser::InvalidArgument, v) if v }
4
+ end
5
+
6
+ alias_method :matches, :matched_by
7
+
8
+ def included_in(*a)
9
+ -> (v) { a[0].include? v ? v : raise(OptionParser::InvalidArgument, v) if v } if a[0].is_a? Range
10
+ -> (v) { a.flatten.include?(v) ? v : raise(OptionParser::InvalidArgument, v) if v }
11
+ end
12
+
13
+ alias_method :in, :included_in
14
+
15
+ def listed_as(t)
16
+ -> (v) do
17
+ begin
18
+ b, p = nil, OptionParser.new.on(:REQUIRED, '--0', t, &-> (i) { b = i })
19
+ (v.is_a?(Array) ? v : v.to_s.split(/,/)).map { |i| p.parse! %W(--0=#{i}) and b } if v
20
+ rescue OptionParser::InvalidArgument
21
+ raise $!.tap { |e| e.args[0] = e.args[0].sub(/\A--\d+=/, '') }
22
+ end
23
+ end
24
+ end
25
+
26
+ alias_method :lists, :listed_as
27
+ end
28
+
29
+ OptionBinder.prepend OptionBinder::Handler
@@ -0,0 +1,37 @@
1
+ require 'optbind'
2
+
3
+ class OptionBinder
4
+ def order(*argv, &blk)
5
+ order! argv.dup.flatten, &blk
6
+ end
7
+
8
+ def order!(argv = parser.default_argv, &blk)
9
+ parse_args! @parser.order! argv, &blk
10
+ end
11
+
12
+ def permute(*argv)
13
+ permute! argv.dup.flatten
14
+ end
15
+
16
+ def permute!(argv = parser.default_argv)
17
+ parse_args! @parser.permute! argv
18
+ end
19
+
20
+ module Arguable
21
+ def order(&blk)
22
+ binder.order self, &blk
23
+ end
24
+
25
+ def order!(&blk)
26
+ binder.order! self, &blk
27
+ end
28
+
29
+ def permute
30
+ binder.permute self
31
+ end
32
+
33
+ def permute!
34
+ binder.permute! self
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,102 @@
1
+ require 'forwardable'
2
+
3
+ require 'optparse/date'
4
+ require 'optparse/shellwords'
5
+ require 'optparse/time'
6
+ require 'optparse/uri'
7
+
8
+ class OptionBinder
9
+ class << self
10
+ extend Forwardable
11
+
12
+ def_delegators OptionParser, :accept, :reject
13
+ end
14
+
15
+ accept(Symbol, /.+/m) { |s, _| s.to_sym if s }
16
+
17
+ # NOTE: Regexp defined in OptionParser does not handle missing argument,
18
+ # therefore it is redefined to work properly on switches with optional argument
19
+
20
+ accept Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*" do |s, p, o|
21
+ f, k = 0, nil
22
+ if o
23
+ f |= Regexp::IGNORECASE if /i/ =~ o
24
+ f |= Regexp::MULTILINE if /m/ =~ o
25
+ f |= Regexp::EXTENDED if /x/ =~ o
26
+ k = o.delete 'imx'
27
+ k = nil if k.empty?
28
+ end
29
+ Regexp.new p || s, f, k if p || s
30
+ end
31
+
32
+ include OptionParser::Acceptables
33
+
34
+ # NOTE: OctalInteger defined in OptionParser does not handle 0o prefix, therefore
35
+ # it is redefined along with Integer and Numeric patterns which use it internally
36
+
37
+ # NOTE: OctalInteger defined in OptionParser for some reason accepts binary and hexadecimal values too,
38
+ # whereas DecimalInteger only accepts decimal values, therefore the OctalInteger is redefined to accept
39
+ # octal values only to unify its behavior with binary, decimal, and hexadecimal integer patterns
40
+
41
+ binary = '(?:0b)?[01]+(?:_[01]+)*'
42
+ octal = '(?:0o?)?[0-7]+(?:_[0-7]+)*'
43
+ decimal = '\d+(?:_\d+)*'
44
+ hexadecimal = '(?:0x)?[\da-f]+(?:_[\da-f]+)*'
45
+
46
+ BinaryInteger = /\A[-+]?#{binary}\z/io
47
+ OctalInteger = /\A[-+]?#{octal}\z/io
48
+ HexadecimalInteger = /\A[-+]?#{hexadecimal}\z/io
49
+
50
+ accept Integer, /\A[-+]?(?:#{binary}|#{octal}|#{hexadecimal}|#{decimal})\z/io do |*a|
51
+ s = a[0]
52
+ begin
53
+ Integer s
54
+ rescue ArgumentError
55
+ raise OptionParser::InvalidArgument, s
56
+ end if s
57
+ end
58
+
59
+ with_base = -> (b) do
60
+ -> (*a) do
61
+ s = a[0]
62
+ begin
63
+ Integer s, b
64
+ rescue ArgumentError
65
+ raise OptionParser::InvalidArgument, s
66
+ end if s
67
+ end
68
+ end
69
+
70
+ accept BinaryInteger, BinaryInteger, &with_base.call(2)
71
+ accept OctalInteger, OctalInteger, &with_base.call(8)
72
+ accept DecimalInteger, DecimalInteger, &with_base.call(10)
73
+ accept HexadecimalInteger, HexadecimalInteger, &with_base.call(16)
74
+
75
+ float = "(?:#{decimal}(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?"
76
+ real = "[-+]?(?:#{binary}|#{octal}|#{hexadecimal}|#{float})"
77
+
78
+ accept Numeric, /\A(#{real})(?:\/(#{real}))?\z/io do |s, d, n|
79
+ if n
80
+ Rational d, n
81
+ elsif s
82
+ eval s
83
+ end
84
+ end
85
+
86
+ # NOTE: ShellWords defined in OptionParser does not handle missing argument,
87
+ # therefore it is redefined to work properly on switches with optional argument
88
+
89
+ ShellWords = Shellwords
90
+
91
+ OptionParser.accept(Shellwords) { |s, *| Shellwords.shellsplit s if s }
92
+
93
+ # NOTE: URI defined in OptionParser parses successfully on an empty argument,
94
+ # therefore it is redefined to work similarly with other complex object types
95
+
96
+ OptionParser.accept(URI) do |s, *|
97
+ if s
98
+ raise OptionParser::InvalidArgument, s if s.empty?
99
+ URI.parse s
100
+ end
101
+ end
102
+ end
@@ -1,3 +1,3 @@
1
- module OptBind
2
- VERSION = '0.4.0'
1
+ class OptBind
2
+ VERSION = '0.5.0'
3
3
  end
data/lib/optbind.rb CHANGED
@@ -45,24 +45,23 @@ class OptionBinder
45
45
 
46
46
  def_delegators :@parser, :accept, :reject
47
47
  def_delegators :@parser, :abort, :warn
48
- def_delegators :@parser, :load
48
+ def_delegators :@parser, :environment, :load
49
+ def_delegators :@parser, :to_a, :to_s
49
50
 
50
- def parse(argv)
51
- parse!(argv.dup) and argv
51
+ def parse(*argv)
52
+ parse! argv.dup.flatten
52
53
  end
53
54
 
54
- def parse!(argv)
55
+ def parse!(argv = parser.default_argv)
55
56
  parse_args! @parser.parse! argv
56
57
  end
57
58
 
58
- def_delegators :@parser, :to_a, :to_s
59
-
60
59
  def_delegator :@parser, :program_name, :program
61
60
  def_delegator :@parser, :version
62
61
  def_delegator :@parser, :help
63
62
 
64
63
  def usage(*args)
65
- line = (args * ' ') << "\n"
64
+ line = (args.flatten * ' ') << "\n"
66
65
 
67
66
  if @parser.banner =~ /\Ausage:.+\n\n/i
68
67
  @parser.banner = "usage: #{program} #{line}"
@@ -78,10 +77,11 @@ class OptionBinder
78
77
 
79
78
  def option(*opts, &handler)
80
79
  opts, handler, bound, variable, default = *several_variants(*opts, &handler)
80
+ o, h = *loot!(opts, &handler)
81
81
 
82
- @parser.on(*opts) do |r|
82
+ @parser.on(*o) do |r|
83
83
  raise OptionParser::InvalidArgument if opts.include?(:REQUIRED) && (r.nil? || r.respond_to?(:empty?) && r.empty?)
84
- handle! handler, r, bound, variable, default
84
+ handle! h, r, bound, variable, default
85
85
  end
86
86
 
87
87
  (@option_definitions ||= []) << { opts: opts, handler: handler, bound: bound, variable: variable }
@@ -93,10 +93,11 @@ class OptionBinder
93
93
 
94
94
  def argument(*opts, &handler)
95
95
  opts, handler, bound, variable, default = *several_variants(*opts, &handler)
96
+ o, h = *loot!(opts, &handler)
96
97
 
97
- (@argument_parser ||= OptionParser.new).on(*(opts << "--#{(@argument_definitions || []).size}")) do |r|
98
+ (@argument_parser ||= OptionParser.new).on(*(o + ["--#{(@argument_definitions || []).size}"])) do |r|
98
99
  raise OptionParser::InvalidArgument if opts.include?(:REQUIRED) && (r.nil? || r.respond_to?(:empty?) && r.empty?)
99
- handle! handler, r, bound, variable, default
100
+ handle! h, r, bound, variable, default
100
101
  end
101
102
 
102
103
  (@argument_definitions ||= []) << { opts: opts, handler: handler, bound: bound, variable: variable }
@@ -137,11 +138,8 @@ class OptionBinder
137
138
 
138
139
  module Switch
139
140
  def self.parser_opts_from_hash(hash = {}, &handler)
140
- style = case (hash[:style] || hash[:mode]).to_s.downcase
141
- when 'required' then :REQUIRED
142
- when 'optional' then :OPTIONAL
143
- end
144
-
141
+ p = (hash[:style] || hash[:mode]).to_s.upcase
142
+ style = [(p.to_sym if %w(REQUIRED OPTIONAL).include? p), (:MULTIPLE if hash[:multiple])]
145
143
  pattern = hash[:pattern] || hash[:type]
146
144
  values = hash[:values]
147
145
  names = [hash[:long], hash[:longs]].flatten.map { |n| n.to_s.sub(/\A-{,2}/, '--') if n }
@@ -156,7 +154,7 @@ class OptionBinder
156
154
  argument = (hash[:argument].to_s.sub(/\A(\[)?=?/, '=\1') if hash[:argument])
157
155
  description = ([hash[:description]].flatten * ' ' if hash[:description])
158
156
  handler ||= hash[:handler]
159
- return ([style, pattern, values] + names + [argument, description]).compact, handler
157
+ return (style + [pattern, values] + names + [argument, description]).compact, handler
160
158
  end
161
159
 
162
160
  def self.parser_opts_from_string(string = '', &handler)
@@ -166,27 +164,27 @@ class OptionBinder
166
164
  shorts << $~[:short]
167
165
  end
168
166
 
169
- style, pattern, values, argument = nil
167
+ style, pattern, values, argument = [], nil
170
168
 
171
169
  while string.sub!(/\A(?:(?<long>--[\[\]\-\w]+[\]\w]+)?(?:(?<argument>(?:\[?=?|=\[)[<(]\S+[)>]\.{,3}\]?)|\s+))/, '')
172
170
  longs << $~[:long] if $~[:long]
173
171
  next unless $~[:argument]
174
172
  argument = $~[:argument]
175
- style = argument =~ /\A=?[<(]/ ? :REQUIRED : :OPTIONAL
176
- pattern = Array if argument =~ /\.{3}\]?\z/
173
+ style = [argument =~ /\A=?[<(]/ ? :REQUIRED : :OPTIONAL, argument =~ /\.{3}\]?\z/ ? :MULTIPLE : nil]
177
174
  values = $~[:values].split('|') if argument =~ /(?:\[?=?|=\[)\((?<values>\S*)\)\]?/
178
175
 
179
176
  if values.nil? && argument =~ /(?:\[?=?|=\[)<(?<name>\S+):(?<pattern>\S+)>\]?/
180
- pattern = Module.const_get($~[:pattern]) rescue Regexp.new($~[:pattern])
181
- argument = "=<#{$~[:name]}>"
182
- argument = "=[#{argument[1..-1]}]" if style == :OPTIONAL
177
+ argument, pattern = "=<#{$~[:name]}>#{'...' if style.include? :MULTIPLE}", $~[:pattern]
178
+ argument = "=[#{argument[1..-1]}]" if style.include? :OPTIONAL
179
+ pattern = pattern.gsub(/\//, '::').gsub(/(?:\A|[-_]+)\w/) { |p| p[-1].upcase } if pattern =~ /\A[-_a-z]/
180
+ pattern = OptionBinder.const_get(pattern) rescue Regexp.new(pattern)
183
181
  else
184
- argument.sub!(/\A(?:=\[|\[?=?)/, style == :OPTIONAL ? '=[' : '=')
182
+ argument.sub!(/\A(?:=\[|\[?=?)/, style.include?(:OPTIONAL) ? '=[' : '=')
185
183
  end
186
184
  end
187
185
 
188
186
  description = !string.empty? ? string.strip : nil
189
- return ([style, pattern, values] + shorts + longs + [argument, description]).compact, handler
187
+ return (style + [pattern, values] + shorts + longs + [argument, description]).compact, handler
190
188
  end
191
189
  end
192
190
 
@@ -196,8 +194,9 @@ class OptionBinder
196
194
  if opts.size == 1
197
195
  case opts[0]
198
196
  when Hash
199
- hash, variable = opts[0], [hash.delete(:variable), hash.delete(:bind)].compact[0]
200
- bound = !(opts[:bound] === false) && !!variable
197
+ hash = opts[0].dup
198
+ variable = hash.delete(:variable) || hash.delete(:bind)
199
+ bound = !(opts.delete(:bound) === false) && !!variable
201
200
  default = hash.delete(:default) || (@reader.call(variable.to_sym) if variable)
202
201
  opts, handler = Switch.parser_opts_from_hash hash, &handler
203
202
  when String
@@ -227,12 +226,13 @@ class OptionBinder
227
226
 
228
227
  def parse_args!(argv)
229
228
  return argv unless @argument_parser
230
- k = @argument_definitions.find_index { |a| a[:opts].include? Array }
229
+ k = @argument_definitions.find_index { |a| a[:opts].include? :MULTIPLE }
231
230
  p = k ? argv[0...k].map { |r| [r] } << argv[k..-1] : argv.map { |r| [r] }
232
231
  p = (p.empty? ? p << [] : p).each_with_index.map do |r, i|
233
232
  a = @argument_definitions[i]
234
233
  raise TooManyArguments unless a
235
234
  raise MissingArguments if a[:opts].include?(:REQUIRED) && r.empty?
235
+ # TODO test escapes!
236
236
  "--#{i}=#{r * ','}" if a[:opts].include?(:REQUIRED) || !(r.empty? || r.find(&:empty?))
237
237
  end
238
238
  @argument_parser.order! p
@@ -244,6 +244,16 @@ class OptionBinder
244
244
 
245
245
  private :parse_args!
246
246
 
247
+ def loot!(opts, &handler)
248
+ return opts, handler unless opts.include? :MULTIPLE
249
+ o = opts.dup
250
+ t = o.delete o.find { |a| a != Array && a.is_a?(Module) }
251
+ o << Array unless o.include? Array
252
+ return o, handler unless t
253
+ require 'optbind/handler'
254
+ return o, -> (a) { (handler || -> (r) { r }).call listed_as(t).call a }
255
+ end
256
+
247
257
  def handle!(handler, raw, bound, variable, default)
248
258
  (handler || -> (r) { r }).call(raw == nil ? default : raw).tap do |x|
249
259
  return x unless bound
@@ -252,16 +262,16 @@ class OptionBinder
252
262
  end
253
263
  end
254
264
 
255
- private :handle!
265
+ private :loot!, :handle!
256
266
 
257
267
  module Arguable
268
+ def self.extend_object(o)
269
+ super and return unless o.singleton_class.included_modules.include? OptionParser::Arguable
270
+ %i(order! permute!).each { |m| o.define_singleton_method(m) { raise 'unsupported' }}
271
+ end
272
+
258
273
  def binder=(bind)
259
- unless @optbind = bind
260
- class << self
261
- undef_method(:binder)
262
- undef_method(:binder=)
263
- end
264
- end
274
+ @optbind = bind
265
275
  end
266
276
 
267
277
  def binder(opts = {}, &blk)
@@ -276,11 +286,16 @@ class OptionBinder
276
286
  @optbind = OptionBinder.new parser: opts[:parser], target: target, bind: bind
277
287
  end
278
288
 
289
+ @optbind.parser.default_argv = self
279
290
  @optbind.instance_eval &blk if blk
280
- self.options = @optbind.parser
291
+ self.options = @optbind.parser if respond_to? :options=
281
292
  @optbind
282
293
  end
283
294
 
295
+ extend Forwardable
296
+
297
+ def_delegators :binder, :parser, :target
298
+
284
299
  alias_method :define, :binder
285
300
  alias_method :define_and_bind, :binder
286
301
  alias_method :bind, :binder
@@ -297,10 +312,6 @@ class OptionBinder
297
312
 
298
313
  alias_method :bind_and_parse!, :define_and_parse!
299
314
 
300
- def parser
301
- self.options
302
- end
303
-
304
315
  def parse
305
316
  binder.parse self
306
317
  end
@@ -314,3 +325,5 @@ end
314
325
  ARGV.extend(OptionBinder::Arguable)
315
326
 
316
327
  OptBind = OptionBinder
328
+
329
+ require 'optbind/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optbind
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavol Zbell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-23 00:00:00.000000000 Z
11
+ date: 2016-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.10.0
41
- description: Binds command-line options to variables. Supports binding of options
42
- and arguments, default values, and partial argument analysis.
41
+ description: Binds command-line options to objects or variables. Supports binding
42
+ of options and arguments, default values, and argument analysis.
43
43
  email:
44
44
  - pavol.zbell@gmail.com
45
45
  executables: []
@@ -51,6 +51,10 @@ files:
51
51
  - README.md
52
52
  - TODO.md
53
53
  - lib/optbind.rb
54
+ - lib/optbind/default.rb
55
+ - lib/optbind/handler.rb
56
+ - lib/optbind/mode.rb
57
+ - lib/optbind/type.rb
54
58
  - lib/optbind/version.rb
55
59
  homepage: https://github.com/pavolzbell/optbind
56
60
  licenses:
@@ -75,5 +79,5 @@ rubyforge_project:
75
79
  rubygems_version: 2.5.1
76
80
  signing_key:
77
81
  specification_version: 4
78
- summary: Binds command-line options to variables.
82
+ summary: Binds command-line options to objects or variables.
79
83
  test_files: []