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 +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +99 -11
- data/TODO.md +6 -8
- data/lib/optbind/default.rb +5 -0
- data/lib/optbind/handler.rb +29 -0
- data/lib/optbind/mode.rb +37 -0
- data/lib/optbind/type.rb +102 -0
- data/lib/optbind/version.rb +2 -2
- data/lib/optbind.rb +53 -40
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 460ca3303bb62d3d9082fad5a691d3c96d0c68a7
|
|
4
|
+
data.tar.gz: 279ef0028459cb0b10e90f4b25421d5894cb19eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
[](https://travis-ci.org/pavolzbell/optbind)
|
|
4
4
|
[](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
|
|
10
|
-
to define command
|
|
11
|
-
|
|
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`
|
|
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
|
-
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
-
|
|
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
|
-
-
|
|
12
|
-
-
|
|
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,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
|
data/lib/optbind/mode.rb
ADDED
|
@@ -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
|
data/lib/optbind/type.rb
ADDED
|
@@ -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
|
data/lib/optbind/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
VERSION = '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!
|
|
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(*
|
|
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!
|
|
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(*(
|
|
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!
|
|
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
|
-
|
|
141
|
-
|
|
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 (
|
|
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 =
|
|
181
|
-
argument = "
|
|
182
|
-
|
|
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
|
|
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 (
|
|
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
|
|
200
|
-
|
|
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?
|
|
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
|
-
|
|
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
|
+
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-
|
|
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
|
|
42
|
-
and arguments, default values, and
|
|
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: []
|