optbind 0.0.1 → 0.0.2

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: c395c64551b84df7175dfec28a79be4c22f8c573
4
- data.tar.gz: 74866508882f4f8fdf85255742843f17d7081998
3
+ metadata.gz: 08d6fbf553b3406e305a2b8f2f238d322af9f940
4
+ data.tar.gz: 6bbb9c352093c33304bf7efd1b9e28b58f4fae89
5
5
  SHA512:
6
- metadata.gz: 1e2d3ebdbad19da59c35a9662aeb3d2d30615ed00331b301857825e11e16fc54cf5603e5bc1f71615d81c37fa19c6716eb38c7736f025ab2ec13d43e4de4305d
7
- data.tar.gz: 8b53927e37f120ddaad40926ef5d4d7f4d826ac17d4079481a7100a37a342f9d314794c91a327a2ee841cb94321f273692efa085fd8e6045d437b970bc28ee00
6
+ metadata.gz: d836778a595cb58dc5abddd17c975cc043e677250282d54071450034d75e77478361cbe43040b7d922222ee3677d679fceb2ed264598c5a01830022e4e0ea6a0
7
+ data.tar.gz: be2ecf0661478e19f81b3e1904bc243e264e12b5842f54b583bca126a68011bb26620177eb7fa4d42a6384f6debdec55faa600f1d180ab782c795a62ccad8578
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## 0.0.1
2
+
3
+ * Add initial extension to standard command line option parsing, setup Gem environment and RSpec tests
4
+
5
+ *Pavol Zbell*
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Pavol Zbell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # OptBind
2
+
3
+ [![Build Status](https://img.shields.io/travis/pavolzbell/optbind.svg)](https://travis-ci.org/pavolzbell/optbind)
4
+ [![Gem Version](https://img.shields.io/gem/v/optbind.svg)](https://badge.fury.io/gh/pavolzbell/optbind)
5
+
6
+ Binds command-line options to variables.
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).
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.
12
+
13
+ ## Installation
14
+
15
+ bundle install optbind
16
+
17
+ ## Usage
18
+
19
+ Binds local variables to `ARGV` and parses command line arguments:
20
+
21
+ ```ruby
22
+ ARGV #=> ['--no-verbose' '-o', 'file.out', 'file.in']
23
+
24
+ i, o, v = STDIN, STDOUT, true
25
+
26
+ ARGV.bind_and_parse! to: :locals do
27
+ use '[<options>] [<file>]'
28
+ use '--help'
29
+ opt 'o -o --output=<file>'
30
+ opt 'v -v --[no-]verbose'
31
+ arg 'i [<file>]'
32
+ end
33
+
34
+ [i, o, v] #=> ['file.in', 'file.out', false]
35
+ ```
36
+
37
+ See specs for more examples and details on usage.
38
+
39
+ ## Testing
40
+
41
+ bundle exec rspec
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it
46
+ 2. Create your feature branch (`git checkout -b new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin new-feature`)
49
+ 5. Create new Pull Request
50
+
51
+ ## License
52
+
53
+ This software is released under the [MIT License](LICENSE.md)
data/TODO.md ADDED
@@ -0,0 +1,27 @@
1
+ # TODOS
2
+
3
+ - `Switch#parser_opts_from_string` - support regexps with options, like `=<name:/[a-z]/i>`
4
+ - `Switch#parser_opts_from_string` - support ranges, like `=<indent:0..8>`
5
+ - add default support for ranges on `OptionParser` level
6
+ - add type conversion and patterns for arguments
7
+ - make optional extensions for `OptionParser` monkey-patching `make_switch` to support hash-or-string-exclusive arguments
8
+
9
+ # IDEAS
10
+
11
+ gems:
12
+ optparse-struct struct/hash-only API #make_switch_from_struct, #make_switch_from_hash (override make_switch, add to ARGV)
13
+ optparse-string string only API #make_switch_from_string (override make_switch, add to ARGV)
14
+ optparse-usage git-like usage #usage, #use (override to_s, use banner, use version, auto-add --help and --version, add to ARGV)
15
+ optbind bindings to vars #bind, #bind! (bind to local/instance vars, support defaults, add to ARGV)
16
+ opt ??? enhances syntax see syntax def below
17
+ optargs supoort for args
18
+
19
+ - store defaults and make them always accessible
20
+ - support arguments? consider ARGF
21
+
22
+ - bind API:
23
+ must be uninvasive, i.e. must not affect #on in any way, must act as #parse, see example
24
+ ARGV, OptionParser (options)
25
+ example: ARGV.options { |o| o.on(...); o.bind ...; o.parse! }
26
+ on #bind call, #bind goes through all options (objects produced by make_switch) and binds them to variables
27
+ NO: ARGV.bind shortcut just ARGV.options.bind
@@ -0,0 +1,3 @@
1
+ module OptBind
2
+ VERSION = '0.0.2'
3
+ end
data/lib/optbind.rb ADDED
@@ -0,0 +1,297 @@
1
+ require 'forwardable'
2
+ require 'optparse'
3
+
4
+ class OptionBinder
5
+ attr_reader :parser, :target
6
+
7
+ def initialize(parser: nil, target: nil, bind: nil)
8
+ target, bind = TOPLEVEL_BINDING, :to_local_variables if target == nil && bind == nil
9
+ @parser = resolve_parser(parser)
10
+ @target, @reader, @writer = target, *resolve_binding(target, bind)
11
+ yield self if block_given?
12
+ end
13
+
14
+ def resolve_binding(target, bind)
15
+ case bind
16
+ when :to_local_variables
17
+ raise ArgumentError unless target.is_a? Binding
18
+ return -> (v) { target.local_variable_get v }, -> (v, x) { target.local_variable_set v, x }
19
+ when :to_instance_variables
20
+ return -> (v) { target.instance_variable_get "@#{v}" }, -> (v, x) { target.instance_variable_set "@#{v}", x }
21
+ else
22
+ return -> (v) { target[v] }, -> (v, x) { target[v] = x } if target.respond_to? :[]
23
+ return -> (v) { target.public_send v }, -> (v, x) { target.public_send "#{v}=", x }
24
+ end
25
+ end
26
+
27
+ def resolve_parser(parser = nil)
28
+ parser || OptionParser.new do |p|
29
+ p.define_singleton_method(:banner) { (b = super()) !~ /\AU/ ? b : "usage: #{program_name}\n\n" }
30
+ p.define_singleton_method(:version) { super() || (defined?(::VERSION) && ::VERSION) }
31
+ p.define_singleton_method(:help) { super() << " -h, --help\n --version\n\n" }
32
+ end
33
+ end
34
+
35
+ private :resolve_binding, :resolve_parser
36
+
37
+ extend Forwardable
38
+
39
+ def_delegators :@parser, :accept, :reject
40
+ def_delegators :@parser, :abort, :warn
41
+ def_delegators :@parser, :load
42
+
43
+ def order(*argv, &blk)
44
+ @parser.order *argv, &blk
45
+ parse_args argv
46
+ end
47
+
48
+ def order!(argv)
49
+ @parser.order! argv
50
+ parse_args argv
51
+ end
52
+
53
+ def permute(*argv)
54
+ @parser.permute *argv
55
+ parse_args argv
56
+ end
57
+
58
+ def permute!(argv)
59
+ @parser.permute! argv
60
+ parse_args argv
61
+ end
62
+
63
+ def parse(*argv)
64
+ @parser.parse *argv
65
+ parse_args argv
66
+ end
67
+
68
+ def parse!(argv)
69
+ @parser.parse! argv
70
+ parse_args! argv
71
+ end
72
+
73
+ def_delegators :@parser, :to_a, :to_s
74
+
75
+ def_delegator :@parser, :program_name, :program
76
+ def_delegator :@parser, :version
77
+ def_delegator :@parser, :help
78
+
79
+ def usage(*args)
80
+ line = (args * ' ') << "\n"
81
+
82
+ if @parser.banner =~ /\Ausage:.+\n\n/i
83
+ @parser.banner = "usage: #{program} " << line
84
+ @parser.separator "\n"
85
+ else
86
+ @parser.banner << " or: #{program} " << line
87
+ end
88
+
89
+ self
90
+ end
91
+
92
+ alias_method :use, :usage
93
+
94
+ def option(*opts, &handler)
95
+ opts, handler, bound, variable, default = *several_variants(*opts, &handler)
96
+
97
+ @parser.on(*opts) do |r|
98
+ unless opts.include? :OPTIONAL
99
+ a = opts.select { |o| o =~ /\A-/ }.sort_by { |o| o.length }[-1]
100
+ @parser.abort "missing argument: #{a}=" if !r || (r.respond_to?(:empty?) && r.empty?)
101
+ end
102
+
103
+ (handler || -> (_) { r }).call(r == nil ? default : r).tap { |x| @writer.call variable, x if bound }
104
+ end
105
+
106
+ (@bound_variables_with_defaults ||= {})[variable] = default if bound
107
+ self
108
+ end
109
+
110
+ alias_method :opt, :option
111
+
112
+ def argument(*opts, &handler)
113
+ opts, handler, bound, variable, default = *several_variants(*opts, &handler)
114
+
115
+ opts.each do |opt|
116
+ (opts << :MULTIPLE) and break if opt.to_s =~ /<\S+>\.{3}/
117
+ end
118
+
119
+ (@argument_definitions ||= []) << { opts: opts, handler: handler, bound: bound, variable: variable }
120
+ (@bound_variables_with_defaults ||= {})[variable] = default if bound
121
+ self
122
+ end
123
+
124
+ alias_method :arg, :argument
125
+
126
+ def bound_defaults
127
+ @bound_variables_with_defaults ? @bound_variables_with_defaults.dup : {}
128
+ end
129
+
130
+ def bound_variables
131
+ return {} unless @bound_variables_with_defaults
132
+ Hash[@bound_variables_with_defaults.keys.map { |v| [v, @reader.call(v)] }]
133
+ end
134
+
135
+ def default?(v)
136
+ v = v.to_sym
137
+ return nil unless (@bound_variables_with_defaults || {}).has_key? v
138
+ @bound_variables_with_defaults[v] == @reader.call(v)
139
+ end
140
+
141
+ module Switch
142
+ def self.parser_opts_from_hash(hash = {}, &handler)
143
+ style = case (hash[:style] || hash[:mode]).to_s.downcase
144
+ when 'required' then :REQUIRED
145
+ when 'optional' then :OPTIONAL
146
+ end
147
+
148
+ pattern = hash[:pattern] || hash[:type]
149
+ values = hash[:values]
150
+
151
+ names = [hash[:long], hash[:longs]].flatten.map { |n| n.to_s.sub(/\A-{,2}/, '--') if n }
152
+ names += [hash[:short], hash[:shorts]].flatten.map { |n| n.to_s.sub(/\A-{,2}/, '-') if n }
153
+ names += [hash[:name], hash[:names]].flatten.map do |n|
154
+ next unless n
155
+ n = n.to_s
156
+ next n if n[0] == '-'
157
+ n[2] ? "--#{n}" : "-#{n}"
158
+ end
159
+
160
+ argument = (hash[:argument].to_s if hash[:argument])
161
+ description = ([hash[:description]].flatten * ' ' if hash[:description])
162
+ handler ||= hash[:handler]
163
+ return ([style, pattern, values] + names + [argument, description]).compact, handler
164
+ end
165
+
166
+ def self.parser_opts_from_string(string = '', &handler)
167
+ shorts, longs = [], []
168
+
169
+ while string.sub!(/\A(?:(?<short>-\w)\s+)/, '')
170
+ shorts << $~[:short]
171
+ end
172
+
173
+ style, pattern, values, argument = nil
174
+
175
+ while string.sub!(/\A(?:(?<long>--[\[\]\-\w]+[\]\w]+)?(?:(?<argument>\[?=[<(]\S+[)>]\.{,3}\]?)|\s+))/, '')
176
+ longs << $~[:long]
177
+ argument = $~[:argument]
178
+
179
+ next unless argument
180
+
181
+ style = argument[0] == '=' ? :REQUIRED : :OPTIONAL
182
+ values = $~[:values].split('|') if argument =~ /\[?=\((?<values>\S*)\)\]?/
183
+
184
+ if values.nil? && argument =~ /\[?=<(?<name>\S+):(?<pattern>\S+)>\]?/
185
+ pattern = Module.const_get($~[:pattern]) rescue Regexp.new($~[:pattern])
186
+ argument = "=<#{$~[:name]}>"
187
+ argument = "[#{argument}]" if style == :OPTIONAL
188
+ end
189
+ end
190
+
191
+ description = !string.empty? ? string.strip : nil
192
+ return ([style, pattern, values] + shorts + longs + [argument, description]).compact, handler
193
+ end
194
+ end
195
+
196
+ def several_variants(*opts, &handler)
197
+ bound, variable, default = false, nil, nil
198
+
199
+ if opts.size == 1
200
+ case opts[0]
201
+ when Hash
202
+ hash, variable = opts[0], [hash.delete(:variable), hash.delete(:bind)].compact[0]
203
+ bound = !(opts[:bound] === false) && !!variable
204
+ default = hash.delete(:default) || (@reader.call(variable.to_sym) if variable)
205
+ opts, handler = Switch.parser_opts_from_hash hash, &handler
206
+ when String
207
+ string, variable = *(opts[0] !~ /\A\s*-/ ? opts[0].split(/\s+/, 2).reverse : [opts[0], nil])
208
+ bound, default = !!variable, (@reader.call(variable.to_sym) if variable)
209
+ opts, handler = Switch.parser_opts_from_string string, &handler
210
+ end
211
+ end
212
+
213
+ variable = variable.to_sym if variable
214
+ return opts, handler, bound, variable, default
215
+ end
216
+
217
+ private :several_variants
218
+
219
+ def parse_args(argv)
220
+ parse_args! argv.dup
221
+ end
222
+
223
+ def parse_args!(argv)
224
+ return argv unless @argument_definitions
225
+ @argument_definitions.each do |a|
226
+ default = (@bound_variables_with_defaults ||= {})[a[:variable]]
227
+ r = argv[0] ? argv.shift : default
228
+ r = ([r] + argv.shift(argv.size)) if a[:opts].include? :MULTIPLE
229
+ @parser.abort 'missing arguments' if r.nil? && args[:opts].include?(:REQUIRED)
230
+ (a[:handler] || -> (_) { r }).call(r == nil ? default : r).tap { |x| @writer.call a[:variable], x if a[:bound] }
231
+ return argv if a[:opts].include? :MULTIPLE
232
+ end
233
+ @parser.abort 'too many arguments' if argv[0]
234
+ argv
235
+ end
236
+
237
+ private :parse_args, :parse_args!
238
+
239
+ module Arguable
240
+ def binder=(bind)
241
+ unless @optbind = bind
242
+ class << self
243
+ undef_method(:binder)
244
+ undef_method(:binder=)
245
+ end
246
+ end
247
+ end
248
+
249
+ def binder(opts = {}, &blk)
250
+ unless @optbind
251
+ if opts[:to] == :locals
252
+ target, bind = TOPLEVEL_BINDING, :to_local_variables
253
+ else
254
+ target = opts[:target] || opts[:to]
255
+ bind = (:to_local_variables if opts[:locals]) || opts[:bind] || ("to_#{opts[:via]}".to_sym if opts[:via])
256
+ end
257
+
258
+ @optbind = OptionBinder.new parser: opts[:parser], target: target, bind: bind
259
+ end
260
+
261
+ @optbind.instance_eval &blk if blk
262
+ self.options = @optbind.parser
263
+ @optbind
264
+ end
265
+
266
+ alias_method :define, :binder
267
+ alias_method :define_and_bind, :binder
268
+ alias_method :bind, :binder
269
+
270
+ def define_and_parse!(opts = {}, &blk)
271
+ define opts, &blk
272
+ parse!
273
+ end
274
+
275
+ alias_method :bind_and_parse!, :define_and_parse!
276
+
277
+ def parser
278
+ self.options
279
+ end
280
+
281
+ def order!(&blk)
282
+ binder.order! self, &blk
283
+ end
284
+
285
+ def permute!
286
+ binder.permute! self
287
+ end
288
+
289
+ def parse!
290
+ binder.parse! self
291
+ end
292
+ end
293
+ end
294
+
295
+ ARGV.extend(OptionBinder::Arguable)
296
+
297
+ OptBind = OptionBinder
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.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavol Zbell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-30 00:00:00.000000000 Z
11
+ date: 2015-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.10.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 0.10.0
69
69
  description: Binds command-line options to variables. Supports binding of options
70
70
  and arguments, default values, and partial argument analysis.
71
71
  email:
@@ -73,7 +73,13 @@ email:
73
73
  executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
- files: []
76
+ files:
77
+ - CHANGELOG.md
78
+ - LICENSE.md
79
+ - README.md
80
+ - TODO.md
81
+ - lib/optbind.rb
82
+ - lib/optbind/version.rb
77
83
  homepage: https://github.com/pavolzbell/optbind
78
84
  licenses:
79
85
  - MIT
@@ -86,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
92
  requirements:
87
93
  - - ">="
88
94
  - !ruby/object:Gem::Version
89
- version: '0'
95
+ version: 2.2.0
90
96
  required_rubygems_version: !ruby/object:Gem::Requirement
91
97
  requirements:
92
98
  - - ">="