optbind 0.3.2 → 0.4.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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +182 -2
  3. data/TODO.md +10 -7
  4. data/lib/optbind/version.rb +1 -1
  5. data/lib/optbind.rb +59 -37
  6. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4356b32d38251a6d9944f9b76cb4f67082c871c
4
- data.tar.gz: d6f981ffc6296a2c4ea58ce98f02e62f59a985eb
3
+ metadata.gz: a75ae614f40e93666809a3bcefac9e58ed61e699
4
+ data.tar.gz: d3a138e3f55237c587aa1d1901f529c3415afa7a
5
5
  SHA512:
6
- metadata.gz: 5c01afa32bb8a831976300d085c554139f2a93acc171c4df0dd71a94604e16c33a404b1db093c016bc0d31a304522b1d74b5dadd92497c1f62001e8dee84c85f
7
- data.tar.gz: fecba5909fb7c7cbfd64573f0b90648dc4e59d06e176934e4454e319b307e050ee751248f4f13aa44798b9a0fcf534585b431e16b6915fc00be107f871418eeb
6
+ metadata.gz: c487f3899ff91d0a18a25647ffe10ae32dcf2cc0bf3b88aa524bb993bea749b271623b3be965970e56169bf5bd87afcc384bbf67c7f3b2b31be59bf64d45eff0
7
+ data.tar.gz: 2f52caa014b1fb9b66a228e1c557ba3daef0b3d220a392671880a46207fbcc71675e8184d34e2a25a7f88384e0faf88e5109baf6d4f0f584c2e7655ea026f31f
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Binds command-line options to variables.
7
7
 
8
- Extends command-line option analysis by wrapping an instance of standard [`OptionParser`](http://ruby-doc.org/stdlib-2.2.3/libdoc/optparse/rdoc/OptionParser.html).
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
9
  Enables binding of options and arguments to instance or local variables. Provides `Hash` and `String` only interfaces
10
10
  to define command line options, unlike a mixed interface by standard library. Supports access to default values and
11
11
  partial argument analysis. Builds Git-like options and help by default.
@@ -21,7 +21,7 @@ Bind local variables to `ARGV` and parse command line arguments:
21
21
  ```ruby
22
22
  require 'optbind'
23
23
 
24
- ARGV #=> ['--no-verbose', '-o', 'file.out', 'file.in']
24
+ ARGV #=> ['--no-verbose', '-o', 'file.out', 'file.in']
25
25
 
26
26
  i, o, v = STDIN, STDOUT, true
27
27
 
@@ -33,11 +33,191 @@ ARGV.bind_and_parse! to: :locals do
33
33
  arg 'i [<file>]'
34
34
  end
35
35
 
36
+ ARGV #=> []
37
+
36
38
  [i, o, v] #=> ['file.in', 'file.out', false]
37
39
  ```
38
40
 
41
+ Use `bind` or one of its aliases on `ARGV` instead of `bind_and_parse!` to be able to `parse!` command line arguments later:
42
+
43
+ ```ruby
44
+ ARGV.bind to: :locals do
45
+ # ...
46
+ end
47
+
48
+ ARGV.parse!
49
+ ```
50
+
51
+ Create an `OptionBinder` and use it directly:
52
+
53
+ ```ruby
54
+ binder = OptionBinder.new do
55
+ # ...
56
+ end
57
+
58
+ binder.parse! ARGV
59
+ ```
60
+
61
+ Note that plain `OptionBinder.new` binds to local variables of top level binding object by default.
62
+
39
63
  See specs for more examples and details on usage.
40
64
 
65
+ ### Bindings
66
+
67
+ Various binding possibilities include:
68
+
69
+ #### Bind to `Hash` object
70
+
71
+ Create target:
72
+
73
+ ```ruby
74
+ options = { input: STDIN, output: STDOUT }
75
+ ```
76
+
77
+ Use `OptionBinder` directly:
78
+
79
+ ```ruby
80
+ OptionBinder.new(target: options) do
81
+ # ...
82
+ end
83
+ ```
84
+
85
+ Use `ARGV` shortcut:
86
+
87
+ ```ruby
88
+ ARGV.define_and_bind(to: options) do
89
+ # ...
90
+ end
91
+ ```
92
+
93
+ #### Bind to public accessors
94
+
95
+ Create target:
96
+
97
+ ```ruby
98
+ class Options
99
+ attr_accessor :input, :output
100
+
101
+ def initialize
102
+ @input, @output = STDIN, STDOUT
103
+ end
104
+ end
105
+ ```
106
+
107
+ Use `OptionBinder` directly:
108
+
109
+ ```ruby
110
+ OptionBinder.new(target: options) do
111
+ # ...
112
+ end
113
+ ```
114
+
115
+ Use `ARGV` shortcut:
116
+
117
+ ```ruby
118
+ ARGV.define_and_bind(to: options) do
119
+ # ...
120
+ end
121
+ ```
122
+
123
+ #### Bind to class variables
124
+
125
+ Create target:
126
+
127
+ ```ruby
128
+ class Options
129
+ @@input, @@output = STDIN, STDOUT
130
+ end
131
+
132
+ options = Options.new
133
+ ```
134
+
135
+ Use `OptionBinder` directly:
136
+
137
+ ```ruby
138
+ OptionBinder.new(target: options, bind: :to_class_variables) do
139
+ # ...
140
+ end
141
+ ```
142
+
143
+ Use `ARGV` shortcut:
144
+
145
+ ```ruby
146
+ ARGV.define_and_bind(to: options, via: :class_variables) do
147
+ # ...
148
+ end
149
+ ```
150
+
151
+ #### Bind to instance variables
152
+
153
+ Create target:
154
+
155
+ ```ruby
156
+ class Options
157
+ def initialize
158
+ @input, @output = STDIN, STDOUT
159
+ end
160
+ end
161
+
162
+ options = Options.new
163
+ ```
164
+
165
+ Use `OptionBinder` directly:
166
+
167
+ ```ruby
168
+ OptionBinder.new(target: options, bind: :to_instance_variables) do
169
+ # ...
170
+ end
171
+ ```
172
+
173
+ Use `ARGV` shortcut:
174
+
175
+ ```ruby
176
+ ARGV.define_and_bind(to: options, via: :instance_variables) do
177
+ # ...
178
+ end
179
+ ```
180
+
181
+ #### Bind to local variables
182
+
183
+ Create target:
184
+
185
+ ```ruby
186
+ input, output = STDIN, STDOUT
187
+ ```
188
+
189
+ Use `OptionBinder` directly:
190
+
191
+ ```ruby
192
+ OptionBinder.new(target: TOPLEVEL_BINDING, bind: :to_local_variables) do
193
+ # ...
194
+ end
195
+ ```
196
+
197
+ Use `OptionBinder` directly with top level binding object as target by default:
198
+
199
+ ```ruby
200
+ OptionBinder.new do
201
+ # ...
202
+ end
203
+ ```
204
+
205
+ Use `ARGV` shortcut:
206
+
207
+ ```ruby
208
+ ARGV.define_and_bind(to: TOPLEVEL_BINDING, via: :local_variables) do
209
+ # ...
210
+ end
211
+ ```
212
+
213
+ Use `ARGV` shortcut with top level binding object as target by default:
214
+
215
+ ```ruby
216
+ ARGV.define_and_bind(to: :locals) do
217
+ # ...
218
+ end
219
+ ```
220
+
41
221
  ## Testing
42
222
 
43
223
  bundle exec rspec
data/TODO.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # TODOS
2
2
 
3
+ - move order and permute to separate package, required on demand
4
+
3
5
  - `Switch#parser_opts_from_string` - support regexps with options, like `=<name:/[a-z]/i>`
4
6
  - `Switch#parser_opts_from_string` - support ranges, like `=<indent:0..8>`
5
- - add default support for ranges on `OptionParser` level
6
- - add support for `Module#class_variables`
7
- - add type conversion and patterns for arguments
7
+
8
+ - add custom type definitions to separate package, required on demand
9
+ - add support for ranges on `OptionParser` level
10
+
11
+ - add optbind/handlers from extise as separate package, required on demand
12
+ - add optbind/defaults from extise as separate package, required on demand
13
+
8
14
  - make optional extensions for `OptionParser` monkey-patching `make_switch` to support hash-or-string-exclusive arguments
9
15
 
10
16
  # IDEAS
@@ -17,10 +23,7 @@ gems:
17
23
  opt ??? enhances syntax see syntax def below
18
24
  optargs supoort for args
19
25
 
20
- - store defaults and make them always accessible
21
- - support arguments? consider ARGF
22
-
23
- - bind API:
26
+ bind API:
24
27
  must be uninvasive, i.e. must not affect #on in any way, must act as #parse, see example
25
28
  ARGV, OptionParser (options)
26
29
  example: ARGV.options { |o| o.on(...); o.bind ...; o.parse! }
@@ -1,3 +1,3 @@
1
1
  module OptBind
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/optbind.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'optparse'
3
5
 
@@ -18,6 +20,8 @@ class OptionBinder
18
20
  return -> (v) { target.local_variable_get v }, -> (v, x) { target.local_variable_set v, x }
19
21
  when :to_instance_variables
20
22
  return -> (v) { target.instance_variable_get "@#{v}" }, -> (v, x) { target.instance_variable_set "@#{v}", x }
23
+ when :to_class_variables
24
+ return -> (v) { target.class_variable_get "@@#{v}" }, -> (v, x) { target.class_variable_set "@@#{v}", x }
21
25
  else
22
26
  return -> (v) { target[v] }, -> (v, x) { target[v] = x } if target.respond_to? :[]
23
27
  return -> (v) { target.public_send v }, -> (v, x) { target.public_send "#{v}=", x }
@@ -25,11 +29,14 @@ class OptionBinder
25
29
  end
26
30
 
27
31
  def resolve_parser(parser = nil)
28
- parser || OptionParser.new do |p|
32
+ parser ||= OptionParser.new do |p|
29
33
  p.define_singleton_method(:banner) { (b = super()) !~ /\AU/ ? b : "usage: #{program_name} [<options>]\n\n" }
30
- p.define_singleton_method(:version) { super() || (defined?(::VERSION) && ::VERSION) }
31
34
  p.define_singleton_method(:help) { super().gsub(/(-\S+)=\[/, '\1[=') << " -h, --help\n --version\n\n" }
32
35
  end
36
+ parser.tap do |p|
37
+ p.program_name = ::PROGRAM if defined? ::PROGRAM
38
+ p.version = ::VERSION if defined? ::VERSION
39
+ end
33
40
  end
34
41
 
35
42
  private :resolve_binding, :resolve_parser
@@ -40,8 +47,12 @@ class OptionBinder
40
47
  def_delegators :@parser, :abort, :warn
41
48
  def_delegators :@parser, :load
42
49
 
43
- { order: '*argv, &blk', order!: 'argv', permute: '*argv', permute!: 'argv', parse: '*argv', parse!: 'argv' }.each do |m, a|
44
- class_eval "def #{m} #{a}; @parser.#{m} #{a}; parse_args#{'!' if m =~ /!\z/} argv; rescue OptionParser::ParseError; @parser.abort; end", __FILE__, __LINE__
50
+ def parse(argv)
51
+ parse!(argv.dup) and argv
52
+ end
53
+
54
+ def parse!(argv)
55
+ parse_args! @parser.parse! argv
45
56
  end
46
57
 
47
58
  def_delegators :@parser, :to_a, :to_s
@@ -54,10 +65,10 @@ class OptionBinder
54
65
  line = (args * ' ') << "\n"
55
66
 
56
67
  if @parser.banner =~ /\Ausage:.+\n\n/i
57
- @parser.banner = "usage: #{program} " << line
68
+ @parser.banner = "usage: #{program} #{line}"
58
69
  @parser.separator "\n"
59
70
  else
60
- @parser.banner << " or: #{program} " << line
71
+ @parser.banner += " or: #{program} #{line}"
61
72
  end
62
73
 
63
74
  self
@@ -69,14 +80,11 @@ class OptionBinder
69
80
  opts, handler, bound, variable, default = *several_variants(*opts, &handler)
70
81
 
71
82
  @parser.on(*opts) do |r|
72
- if opts.include? :REQUIRED
73
- a = opts.select { |o| o =~ /\A-/ }.sort_by { |o| o.length }[-1]
74
- @parser.abort "missing argument: #{a}=" if !r || (r.respond_to?(:empty?) && r.empty?)
75
- end
76
-
83
+ raise OptionParser::InvalidArgument if opts.include?(:REQUIRED) && (r.nil? || r.respond_to?(:empty?) && r.empty?)
77
84
  handle! handler, r, bound, variable, default
78
85
  end
79
86
 
87
+ (@option_definitions ||= []) << { opts: opts, handler: handler, bound: bound, variable: variable }
80
88
  (@bound_variables_with_defaults ||= {})[variable] = default if bound
81
89
  self
82
90
  end
@@ -86,8 +94,9 @@ class OptionBinder
86
94
  def argument(*opts, &handler)
87
95
  opts, handler, bound, variable, default = *several_variants(*opts, &handler)
88
96
 
89
- opts.each do |opt|
90
- (opts << :MULTIPLE) and break if opt.to_s =~ /<\S+>\.{3}/
97
+ (@argument_parser ||= OptionParser.new).on(*(opts << "--#{(@argument_definitions || []).size}")) do |r|
98
+ raise OptionParser::InvalidArgument if opts.include?(:REQUIRED) && (r.nil? || r.respond_to?(:empty?) && r.empty?)
99
+ handle! handler, r, bound, variable, default
91
100
  end
92
101
 
93
102
  (@argument_definitions ||= []) << { opts: opts, handler: handler, bound: bound, variable: variable }
@@ -118,12 +127,12 @@ class OptionBinder
118
127
  end
119
128
 
120
129
  def bound?(v)
121
- (@bound_variables_with_defaults || {}).has_key? v.to_sym
130
+ (@bound_variables_with_defaults || {}).key? v.to_sym
122
131
  end
123
132
 
124
133
  def assigned?(v)
125
134
  return nil unless bound? v
126
- (@assigned_variables_with_values || {}).has_key? v.to_sym
135
+ (@assigned_variables_with_values || {}).key? v.to_sym
127
136
  end
128
137
 
129
138
  module Switch
@@ -151,9 +160,9 @@ class OptionBinder
151
160
  end
152
161
 
153
162
  def self.parser_opts_from_string(string = '', &handler)
154
- shorts, longs = [], []
163
+ string, shorts, longs = string.dup, [], []
155
164
 
156
- while string.sub!(/\A(?:(?<short>-\w)\s+)/, '')
165
+ while string.sub!(/\A(?:(?<short>-\w)\s+)/, '') do
157
166
  shorts << $~[:short]
158
167
  end
159
168
 
@@ -164,6 +173,7 @@ class OptionBinder
164
173
  next unless $~[:argument]
165
174
  argument = $~[:argument]
166
175
  style = argument =~ /\A=?[<(]/ ? :REQUIRED : :OPTIONAL
176
+ pattern = Array if argument =~ /\.{3}\]?\z/
167
177
  values = $~[:values].split('|') if argument =~ /(?:\[?=?|=\[)\((?<values>\S*)\)\]?/
168
178
 
169
179
  if values.nil? && argument =~ /(?:\[?=?|=\[)<(?<name>\S+):(?<pattern>\S+)>\]?/
@@ -203,25 +213,36 @@ class OptionBinder
203
213
 
204
214
  private :several_variants
205
215
 
206
- def parse_args(argv)
207
- parse_args! argv.dup
216
+ class MissingArguments < OptionParser::ParseError
217
+ const_set :Reason, 'missing arguments'
218
+ alias_method :message, :reason
219
+ alias_method :to_s, :reason
220
+ end
221
+
222
+ class TooManyArguments < OptionParser::ParseError
223
+ const_set :Reason, 'too many arguments'
224
+ alias_method :message, :reason
225
+ alias_method :to_s, :reason
208
226
  end
209
227
 
210
228
  def parse_args!(argv)
211
- return argv unless @argument_definitions
212
- @argument_definitions.each do |a|
213
- default = (@bound_variables_with_defaults ||= {})[a[:variable]]
214
- r = argv[0] ? argv.shift : default
215
- r = ([r].flatten + argv.shift(argv.size)).compact if a[:opts].include? :MULTIPLE
216
- @parser.abort 'missing arguments' if (r.nil? || (r.is_a?(Array) && r.empty?)) && a[:opts].include?(:REQUIRED)
217
- handle! a[:handler], r, a[:bound], a[:variable], default
218
- return argv if a[:opts].include? :MULTIPLE
229
+ return argv unless @argument_parser
230
+ k = @argument_definitions.find_index { |a| a[:opts].include? Array }
231
+ p = k ? argv[0...k].map { |r| [r] } << argv[k..-1] : argv.map { |r| [r] }
232
+ p = (p.empty? ? p << [] : p).each_with_index.map do |r, i|
233
+ a = @argument_definitions[i]
234
+ raise TooManyArguments unless a
235
+ raise MissingArguments if a[:opts].include?(:REQUIRED) && r.empty?
236
+ "--#{i}=#{r * ','}" if a[:opts].include?(:REQUIRED) || !(r.empty? || r.find(&:empty?))
219
237
  end
220
- @parser.abort 'too many arguments' if argv[0]
238
+ @argument_parser.order! p
239
+ argv.shift argv.size - p.size
221
240
  argv
241
+ rescue OptionParser::InvalidArgument
242
+ raise $!.tap { |e| e.args[0] = e.args[0].sub(/\A--\d+=/, '') }
222
243
  end
223
244
 
224
- private :parse_args, :parse_args!
245
+ private :parse_args!
225
246
 
226
247
  def handle!(handler, raw, bound, variable, default)
227
248
  (handler || -> (r) { r }).call(raw == nil ? default : raw).tap do |x|
@@ -264,9 +285,14 @@ class OptionBinder
264
285
  alias_method :define_and_bind, :binder
265
286
  alias_method :bind, :binder
266
287
 
288
+ def define_and_parse(opts = {}, &blk)
289
+ define(opts, &blk) and parse
290
+ end
291
+
292
+ alias_method :bind_and_parse, :define_and_parse
293
+
267
294
  def define_and_parse!(opts = {}, &blk)
268
- define opts, &blk
269
- parse!
295
+ define(opts, &blk) and parse!
270
296
  end
271
297
 
272
298
  alias_method :bind_and_parse!, :define_and_parse!
@@ -275,12 +301,8 @@ class OptionBinder
275
301
  self.options
276
302
  end
277
303
 
278
- def order!(&blk)
279
- binder.order! self, &blk
280
- end
281
-
282
- def permute!
283
- binder.permute! self
304
+ def parse
305
+ binder.parse self
284
306
  end
285
307
 
286
308
  def parse!
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.3.2
4
+ version: 0.4.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-03-08 00:00:00.000000000 Z
11
+ date: 2016-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  version: '0'
73
73
  requirements: []
74
74
  rubyforge_project:
75
- rubygems_version: 2.4.5.1
75
+ rubygems_version: 2.5.1
76
76
  signing_key:
77
77
  specification_version: 4
78
78
  summary: Binds command-line options to variables.