options_hash 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +172 -0
- data/Rakefile +1 -0
- data/lib/options_hash.rb +204 -0
- data/lib/options_hash/version.rb +3 -0
- data/options_hash.gemspec +25 -0
- data/spec/options_hash_spec.rb +295 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/empty_options.rb +3 -0
- data/spec/support/person.rb +9 -0
- data/spec/support/person_options.rb +10 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 080f15a9148fb2d6250e85f0779d4bf84813e34d
|
4
|
+
data.tar.gz: 7d41b4bc07b434c3c52df25b73c1d6dc2f38bbfc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b7f4cbdfbb969bdee95b86fb23c61b2c31f858f6d3f62e245894691feb91746bef5617c25474971eec0c7b8336f0d6a48c1ce65ad06ecc7d4ddd4f64101e1850
|
7
|
+
data.tar.gz: 8c14f9cd0777ac19bd8a43e33ef562b109a0af748ecb648a9e8a18b2cdc39bb5036d37d2889a0fd93055f0504478d4a0bde4a55b8cfed1bf24f70f27966fd597
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jared Grippe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# OptionsHash
|
2
|
+
|
3
|
+
An OptionsHash is a definition of required and optional keys in an options hash
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'options_hash'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install options_hash
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
A simple use case goes something like this:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'net/http'
|
25
|
+
|
26
|
+
def http_get options
|
27
|
+
options = OptionsHash.parse(options) do
|
28
|
+
required :host, :path
|
29
|
+
optional :port, default: 80
|
30
|
+
end
|
31
|
+
Net::HTTP.get(options.host, options.path, options.port)
|
32
|
+
end
|
33
|
+
|
34
|
+
http_get
|
35
|
+
# => ArgumentError: wrong number of arguments (0 for 1)
|
36
|
+
http_get({})
|
37
|
+
# => ArgumentError: required options: :host, :path
|
38
|
+
http_get(host: 'google.com')
|
39
|
+
# => ArgumentError: required options: :path
|
40
|
+
http_get(host: 'google.com', path: '/')
|
41
|
+
# => "<HTML><HEAD><meta ht..."
|
42
|
+
http_get(host: 'google.com', path: '/', port: 81)
|
43
|
+
# => Net::HTTPRequestTimeOut
|
44
|
+
```
|
45
|
+
|
46
|
+
A possibly more common use case:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class Person
|
50
|
+
class Options < OptionsHash
|
51
|
+
required :name
|
52
|
+
optional :age, default: 31
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize options
|
56
|
+
@options = Options.parse(options)
|
57
|
+
end
|
58
|
+
attr_reader :options
|
59
|
+
end
|
60
|
+
|
61
|
+
me = Person.new({}) # => ArgumentError: required options: :name
|
62
|
+
|
63
|
+
me = Person.new(name: 'Jared Grippe')
|
64
|
+
me.options.name # => "Jared Grippe"
|
65
|
+
me.options.age # => 31
|
66
|
+
me.options.to_hash # => {:name=>"Jared Grippe", :age=>31}
|
67
|
+
|
68
|
+
me = Person.new(name: 'Jared Grippe', age: 25)
|
69
|
+
me.options.name # => "Jared Grippe"
|
70
|
+
me.options.age # => 25
|
71
|
+
```
|
72
|
+
|
73
|
+
Advanced usage:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class PersonOptions < OptionsHash
|
77
|
+
required :name
|
78
|
+
optional :favorite_color
|
79
|
+
optional :species, default: :human
|
80
|
+
end
|
81
|
+
|
82
|
+
PersonOptions.options # => {
|
83
|
+
# :name=>required without default,
|
84
|
+
# :favorite_color=>optional without default,
|
85
|
+
# :species=>optional with default}
|
86
|
+
|
87
|
+
PersonOptions[:name] # => required without default
|
88
|
+
PersonOptions[:name].required? # => true
|
89
|
+
PersonOptions[:name].has_default? # => false
|
90
|
+
PersonOptions[:name].has_default_proc? # => false
|
91
|
+
PersonOptions[:name].default # => nil
|
92
|
+
|
93
|
+
PersonOptions.parse({}) # ArgumentError: required options: :name
|
94
|
+
|
95
|
+
PersonOptions.parse(name: 'Steve')
|
96
|
+
# => #<PersonOptions {:name=>"Steve", :favorite_color=>nil, :species=>:human}>
|
97
|
+
|
98
|
+
PersonOptions.parse(name: 'Steve', favorite_color: 'blue')
|
99
|
+
# => #<PersonOptions {:name=>"Steve", :favorite_color=>"blue", :species=>:human}>
|
100
|
+
|
101
|
+
PersonOptions.parse(name: 'Steve', species: :robot)
|
102
|
+
# => #<PersonOptions {:name=>"Steve", :favorite_color=>"blue", :species=>:robot}>
|
103
|
+
|
104
|
+
options = PersonOptions.parse(name: 'Steve', species: :robot)
|
105
|
+
|
106
|
+
options.name # => "Steve"
|
107
|
+
options.favorite_color # => nil
|
108
|
+
options.species # => :robot
|
109
|
+
|
110
|
+
options.given? :name # => true
|
111
|
+
options.given? :favorite_color # => false
|
112
|
+
options.given? :species # => true
|
113
|
+
|
114
|
+
options.to_hash # => {:name=>"Steve", :favorite_color=>nil, :species=>:robot}
|
115
|
+
|
116
|
+
```
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
All options can take a block to determine their default value:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
class PersonOptions < OptionsHash
|
125
|
+
required(:first_name, :last_name) { |name| name.to_sym }
|
126
|
+
optional :full_name, default: ->{ "#{first_name} #{last_name}" }
|
127
|
+
end
|
128
|
+
|
129
|
+
PersonOptions
|
130
|
+
# => PersonOptions(required: [:first_name, :last_name], optional: [:full_name])
|
131
|
+
|
132
|
+
options = PersonOptions.parse(first_name: 'Jared', last_name: 'Grippe')
|
133
|
+
# => #<PersonOptions {:first_name=>:Jared, :last_name=>:Grippe, :full_name=>"Jared Grippe"}>
|
134
|
+
|
135
|
+
options.given_options
|
136
|
+
# => {:first_name=>"Jared", :last_name=>"Grippe"}
|
137
|
+
|
138
|
+
options[:first_name] # => :Jared
|
139
|
+
options[:last_name] # => :Grippe
|
140
|
+
options[:full_name] # => "Jared Grippe"
|
141
|
+
```
|
142
|
+
|
143
|
+
Option hashes can inherit from other option hashes
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class AnimalOptions < OptionsHash
|
147
|
+
required :species
|
148
|
+
optional :alive, default: true
|
149
|
+
end
|
150
|
+
class DogOptions < AnimalOptions
|
151
|
+
required :name, :breed
|
152
|
+
end
|
153
|
+
|
154
|
+
AnimalOptions
|
155
|
+
# => AnimalOptions(required: [:species], optional: [:alive])
|
156
|
+
|
157
|
+
DogOptions
|
158
|
+
# => DogOptions(required: [:breed, :name, :species], optional: [:alive])
|
159
|
+
|
160
|
+
DogOptions[:alive].default # => true
|
161
|
+
```
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
## Contributing
|
167
|
+
|
168
|
+
1. Fork it
|
169
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
170
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
171
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
172
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/options_hash.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require "options_hash/version"
|
2
|
+
|
3
|
+
class OptionsHash
|
4
|
+
|
5
|
+
class Option
|
6
|
+
def initialize required, default=nil
|
7
|
+
@required, @default = !!required, default
|
8
|
+
end
|
9
|
+
def required?; @required; end
|
10
|
+
attr_reader :default
|
11
|
+
def has_default?
|
12
|
+
!!default
|
13
|
+
end
|
14
|
+
def has_default_proc?
|
15
|
+
default.is_a? Proc
|
16
|
+
end
|
17
|
+
def inspect
|
18
|
+
%(#{required? ? 'required' : 'optional'} #{default ? 'with' : 'without'} default)
|
19
|
+
end
|
20
|
+
alias_method :to_s, :inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.inherited(subclass)
|
24
|
+
subclass.send :extend, ClassMethods
|
25
|
+
subclass.send :include, InstanceMethods
|
26
|
+
subclass
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
alias_method :_new, :new
|
31
|
+
undef_method :new
|
32
|
+
private :_new
|
33
|
+
def parse options, &block
|
34
|
+
block_given? or raise ArgumentError, 'block required', caller(2)
|
35
|
+
Class.new(self, &block).parse(options)
|
36
|
+
rescue ArgumentError => error
|
37
|
+
raise ArgumentError, error.message, caller(2)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
|
43
|
+
def parse options
|
44
|
+
_new options
|
45
|
+
rescue ArgumentError => error
|
46
|
+
raise ArgumentError, error.message, caller(2)
|
47
|
+
end
|
48
|
+
|
49
|
+
def options
|
50
|
+
@options ||= {}
|
51
|
+
(superclass.respond_to?(:options) ? superclass.options : {}).merge @options
|
52
|
+
end
|
53
|
+
|
54
|
+
def option? key
|
55
|
+
keys.include? key
|
56
|
+
end
|
57
|
+
|
58
|
+
def [] key
|
59
|
+
option? key or raise KeyError, "#{key} is not an option", caller(1)
|
60
|
+
options[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
def keys
|
64
|
+
options.keys.to_set
|
65
|
+
end
|
66
|
+
|
67
|
+
def required_keys
|
68
|
+
options.select{|key, option| option.required? }.keys.to_set
|
69
|
+
end
|
70
|
+
|
71
|
+
def optional_keys
|
72
|
+
options.reject{|key, option| option.required? }.keys.to_set
|
73
|
+
end
|
74
|
+
|
75
|
+
def required *options, &block
|
76
|
+
default = extract_default options, &block
|
77
|
+
set_options Option.new(true, default), *options
|
78
|
+
end
|
79
|
+
|
80
|
+
def optional *options, &block
|
81
|
+
default = extract_default options, &block
|
82
|
+
set_options Option.new(false, default), *options
|
83
|
+
end
|
84
|
+
|
85
|
+
def define_attr_readers object, instance_variable_name=:@options
|
86
|
+
self.freeze
|
87
|
+
instance_variable_name = "@#{instance_variable_name.to_s.sub(/@/,'')}"
|
88
|
+
keys.each do |key|
|
89
|
+
object.send(:define_method, "#{key}" ) do
|
90
|
+
instance_variable_get(instance_variable_name)[key]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :_inspect, :inspect
|
96
|
+
private :_inspect
|
97
|
+
|
98
|
+
def class_name
|
99
|
+
name || "OptionsHash:#{_inspect[/^#<Class:(\w+)/,1]}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
inspect = super
|
104
|
+
required_keys = self.required_keys.to_a.sort
|
105
|
+
optional_keys = self.optional_keys.to_a.sort
|
106
|
+
"#{class_name}(required: #{required_keys.inspect}, optional: #{optional_keys.inspect})"
|
107
|
+
end
|
108
|
+
alias_method :to_s, :inspect
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def extract_default options, &block
|
113
|
+
return block if block_given?
|
114
|
+
(options.last.is_a?(Hash) ? options.pop : {})[:default]
|
115
|
+
end
|
116
|
+
|
117
|
+
def set_options definition, *options
|
118
|
+
@options ||= {}
|
119
|
+
options.each do |key|
|
120
|
+
key = key.to_sym
|
121
|
+
@options[key] = definition.dup
|
122
|
+
define_method("#{key}" ){ fetch key }
|
123
|
+
define_method("#{key}?"){ given? key }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
module InstanceMethods
|
130
|
+
def initialize given_options
|
131
|
+
@keys = self.class.keys.freeze
|
132
|
+
@options = self.class.options.freeze
|
133
|
+
@given_options = (given_options || {}).freeze
|
134
|
+
|
135
|
+
unknown_options = @given_options.keys - keys.to_a
|
136
|
+
unknown_options.empty? or raise ArgumentError, "unknown options: #{unknown_options.sort.map(&:inspect).join(', ')}"
|
137
|
+
|
138
|
+
missing_required_options = required_keys.to_a - @given_options.keys
|
139
|
+
missing_required_options.empty? or raise ArgumentError, "required options: #{missing_required_options.sort.map(&:inspect).join(', ')}"
|
140
|
+
|
141
|
+
|
142
|
+
@values = {}
|
143
|
+
keys.to_a.sort.each{|key| send(key) }
|
144
|
+
end
|
145
|
+
attr_reader :keys, :options, :given_options
|
146
|
+
|
147
|
+
def required_keys
|
148
|
+
@options.select do |key, option|
|
149
|
+
option.required?
|
150
|
+
end.keys.to_set
|
151
|
+
end
|
152
|
+
|
153
|
+
def given? key
|
154
|
+
@given_options.key? key
|
155
|
+
end
|
156
|
+
|
157
|
+
def fetch key
|
158
|
+
key = key.to_sym
|
159
|
+
keys.include?(key) or raise KeyError, "#{key} is not an option", caller(1)
|
160
|
+
return @values[key] if @values.key? key
|
161
|
+
|
162
|
+
option = @options[key]
|
163
|
+
default_proc = option.default if option.default && option.default.is_a?(Proc)
|
164
|
+
|
165
|
+
if option.required?
|
166
|
+
value = @given_options[key]
|
167
|
+
value = instance_exec(value, &default_proc) if option.required? && default_proc
|
168
|
+
return @values[key] = value
|
169
|
+
end
|
170
|
+
|
171
|
+
return @values[key] = @given_options[key] if @given_options.key? key
|
172
|
+
return @values[key] = default_proc ? instance_exec(&default_proc) : option.default
|
173
|
+
end
|
174
|
+
alias_method :[], :fetch
|
175
|
+
|
176
|
+
def to_hash
|
177
|
+
keys.each_with_object Hash.new do |key, hash|
|
178
|
+
hash[key] = send(key)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def inspect
|
184
|
+
%(#<#{self.class.class_name} #{to_hash.inspect}>)
|
185
|
+
end
|
186
|
+
alias_method :to_s, :inspect
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# class Foo
|
193
|
+
|
194
|
+
# def whatever options={}
|
195
|
+
# OptionsHash.parse(options) do
|
196
|
+
# required :name
|
197
|
+
# optional :size, default: 42
|
198
|
+
# end
|
199
|
+
# end
|
200
|
+
|
201
|
+
# end
|
202
|
+
|
203
|
+
|
204
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'options_hash/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "options_hash"
|
8
|
+
spec.version = OptionsHash::VERSION
|
9
|
+
spec.authors = ["Jared Grippe"]
|
10
|
+
spec.email = ["jared@deadlyicon.com"]
|
11
|
+
spec.description = %q{A configurable options hash definition}
|
12
|
+
spec.summary = %q{A configurable options hash definition}
|
13
|
+
spec.homepage = "https://github.com/deadlyicon/options_hash"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry-debugger"
|
25
|
+
end
|
@@ -0,0 +1,295 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OptionsHash do
|
4
|
+
subject{ described_class }
|
5
|
+
|
6
|
+
it { should_not respond_to :new }
|
7
|
+
it { should_not respond_to :options }
|
8
|
+
it { should_not respond_to :keys }
|
9
|
+
it { should_not respond_to :required_keys }
|
10
|
+
it { should_not respond_to :optional_keys }
|
11
|
+
it { should_not respond_to :required }
|
12
|
+
it { should_not respond_to :optional }
|
13
|
+
|
14
|
+
its(:name ){ should == 'OptionsHash' }
|
15
|
+
its(:inspect){ should == 'OptionsHash' }
|
16
|
+
its(:to_s ){ should == 'OptionsHash' }
|
17
|
+
|
18
|
+
shared_examples 'an options hash' do
|
19
|
+
it { should_not respond_to :new }
|
20
|
+
it { should respond_to :options }
|
21
|
+
it { should respond_to :keys }
|
22
|
+
it { should respond_to :required_keys }
|
23
|
+
it { should respond_to :optional_keys }
|
24
|
+
it { should respond_to :required }
|
25
|
+
it { should respond_to :optional }
|
26
|
+
end
|
27
|
+
|
28
|
+
shared_examples 'an options hash instance' do
|
29
|
+
its(:inspect){ should =~ %r[#<#{subject.class.class_name} #{subject.to_hash.inspect}>] }
|
30
|
+
its(:to_s ){ should =~ %r[#<#{subject.class.class_name} #{subject.to_hash.inspect}>] }
|
31
|
+
it 'should respond to each key' do
|
32
|
+
subject.keys.each do |key|
|
33
|
+
expect(subject).to respond_to "#{key}"
|
34
|
+
expect(subject).to respond_to "#{key}?"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
describe '.parse' do
|
41
|
+
context 'when given no arguments' do
|
42
|
+
subject{ ->{ OptionsHash.parse } }
|
43
|
+
it { should raise_error ArgumentError, 'wrong number of arguments (0 for 1)' }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when given no block' do
|
47
|
+
subject{ ->{ OptionsHash.parse({}) } }
|
48
|
+
it { should raise_error ArgumentError, 'block required' }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when given insufficient options' do
|
52
|
+
subject{ ->{ OptionsHash.parse({}){ required :name } } }
|
53
|
+
it { should raise_error ArgumentError, 'required options: :name' }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when given unknown options' do
|
57
|
+
subject{ ->{ OptionsHash.parse(foo:1){ } } }
|
58
|
+
it { should raise_error ArgumentError, 'unknown options: :foo' }
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when given valid options' do
|
62
|
+
subject do
|
63
|
+
OptionsHash.parse(name: 'steve') do
|
64
|
+
required :name
|
65
|
+
optional :size, default: 42
|
66
|
+
end
|
67
|
+
end
|
68
|
+
it_behaves_like 'an options hash instance'
|
69
|
+
its(:name){ should eq 'steve' }
|
70
|
+
its(:size){ should eq 42 }
|
71
|
+
its(:to_hash){ should eq(:name=>"steve", :size=>42) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
describe 'OptionsHash(required: [:a, :b], optional: [:c, :d])' do
|
77
|
+
let :options_hash do
|
78
|
+
Class.new(OptionsHash) do
|
79
|
+
required :a
|
80
|
+
required :b do |b|
|
81
|
+
b.inspect
|
82
|
+
end
|
83
|
+
optional :c
|
84
|
+
optional :d, default: ->{ 42 }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
describe '.parse' do
|
88
|
+
context '(nil)' do
|
89
|
+
subject{ ->{ options_hash.parse(nil) } }
|
90
|
+
it{ should raise_error ArgumentError, 'required options: :a, :b' }
|
91
|
+
end
|
92
|
+
context '({})' do
|
93
|
+
subject{ ->{ options_hash.parse({}) } }
|
94
|
+
it{ should raise_error ArgumentError, 'required options: :a, :b' }
|
95
|
+
end
|
96
|
+
context '(a:1)' do
|
97
|
+
subject{ ->{ options_hash.parse(a:1) } }
|
98
|
+
it{ should raise_error ArgumentError, 'required options: :b' }
|
99
|
+
end
|
100
|
+
context '(b:1)' do
|
101
|
+
subject{ ->{ options_hash.parse(b:1) } }
|
102
|
+
it{ should raise_error ArgumentError, 'required options: :a' }
|
103
|
+
end
|
104
|
+
context '(a:1, b:2)' do
|
105
|
+
subject{ options_hash.parse(a:1, b:2) }
|
106
|
+
its(:a){ should eq 1 }
|
107
|
+
its(:b){ should eq '2' }
|
108
|
+
its(:c){ should be_nil }
|
109
|
+
its(:d){ should eq 42 }
|
110
|
+
end
|
111
|
+
context '(a:1, b:2, c:3)' do
|
112
|
+
subject{ options_hash.parse(a:1, b:2, c:3) }
|
113
|
+
its(:a){ should eq 1 }
|
114
|
+
its(:b){ should eq '2' }
|
115
|
+
its(:c){ should eq 3 }
|
116
|
+
its(:d){ should eq 42 }
|
117
|
+
end
|
118
|
+
context '(a:1, b:2, c:3, d:4)' do
|
119
|
+
subject{ options_hash.parse(a:1, b:2, c:3, d:4) }
|
120
|
+
its(:a){ should eq 1 }
|
121
|
+
its(:b){ should eq '2' }
|
122
|
+
its(:c){ should eq 3 }
|
123
|
+
its(:d){ should eq 4 }
|
124
|
+
end
|
125
|
+
context '(a: nil, b: nil, c: nil, d: nil)' do
|
126
|
+
subject{ options_hash.parse(a: nil, b: nil, c: nil, d: nil) }
|
127
|
+
its(:a){ should be_nil }
|
128
|
+
its(:b){ should eq 'nil' }
|
129
|
+
its(:c){ should be_nil }
|
130
|
+
its(:d){ should be_nil }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#define_attr_readers' do
|
135
|
+
it 'should freeze the options hash' do
|
136
|
+
expect(options_hash).to_not be_frozen
|
137
|
+
options_hash.define_attr_readers(Class.new)
|
138
|
+
expect(options_hash).to be_frozen
|
139
|
+
end
|
140
|
+
it "should define attr readers for each option" do
|
141
|
+
options_hash = self.options_hash
|
142
|
+
_class = Class.new
|
143
|
+
_class.send(:define_method, :initialize) do |options|
|
144
|
+
@options = options_hash.parse(options)
|
145
|
+
end
|
146
|
+
options_hash.define_attr_readers(_class)
|
147
|
+
|
148
|
+
expect(_class.new(a:1,b:2,c:3,d:4).a).to eq 1
|
149
|
+
expect(_class.new(a:1,b:2,c:3,d:4).b).to eq '2'
|
150
|
+
expect(_class.new(a:1,b:2,c:3,d:4).c).to eq 3
|
151
|
+
expect(_class.new(a:1,b:2,c:3,d:4).d).to eq 4
|
152
|
+
|
153
|
+
|
154
|
+
options_hash = self.options_hash
|
155
|
+
_class = Class.new
|
156
|
+
_class.send(:define_method, :initialize) do |options|
|
157
|
+
@swordfish = options_hash.parse(options)
|
158
|
+
end
|
159
|
+
options_hash.define_attr_readers(_class, :@swordfish)
|
160
|
+
|
161
|
+
expect(_class.new(a:1,b:2,c:3,d:4).a).to eq 1
|
162
|
+
expect(_class.new(a:1,b:2,c:3,d:4).b).to eq '2'
|
163
|
+
expect(_class.new(a:1,b:2,c:3,d:4).c).to eq 3
|
164
|
+
expect(_class.new(a:1,b:2,c:3,d:4).d).to eq 4
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
describe 'Class.new(OptionsHash)' do
|
171
|
+
subject{ Class.new(OptionsHash) }
|
172
|
+
|
173
|
+
it_behaves_like 'an options hash'
|
174
|
+
|
175
|
+
its(:name ){ should be_nil }
|
176
|
+
its(:inspect){ should =~ %r{OptionsHash:(\w+)\(required: \[\], optional: \[\]\)} }
|
177
|
+
its(:to_s ){ should =~ %r{OptionsHash:(\w+)\(required: \[\], optional: \[\]\)} }
|
178
|
+
|
179
|
+
describe '.parse({})' do
|
180
|
+
subject{ Class.new(OptionsHash).parse({}) }
|
181
|
+
it_behaves_like 'an options hash instance'
|
182
|
+
end
|
183
|
+
describe '.parse(nil)' do
|
184
|
+
subject{ Class.new(OptionsHash).parse(nil) }
|
185
|
+
it_behaves_like 'an options hash instance'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
describe EmptyOptions do
|
192
|
+
subject{ EmptyOptions }
|
193
|
+
|
194
|
+
it_behaves_like 'an options hash'
|
195
|
+
|
196
|
+
its(:name ){ should == 'EmptyOptions' }
|
197
|
+
its(:inspect){ should == %{EmptyOptions(required: [], optional: [])} }
|
198
|
+
its(:to_s ){ should == %{EmptyOptions(required: [], optional: [])} }
|
199
|
+
end
|
200
|
+
|
201
|
+
describe PersonOptions do
|
202
|
+
subject{ PersonOptions }
|
203
|
+
|
204
|
+
it_behaves_like 'an options hash'
|
205
|
+
|
206
|
+
its(:keys){ should eq Set[:name, :level_of_schooling, :height, :weight, :size, :iq, :intelegence] }
|
207
|
+
|
208
|
+
describe %(PersonOptions.parse(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2)) do
|
209
|
+
subject{ PersonOptions.parse(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2) }
|
210
|
+
|
211
|
+
it_behaves_like 'an options hash instance'
|
212
|
+
|
213
|
+
its(:name? ){ should be_true }
|
214
|
+
its(:level_of_schooling? ){ should be_true }
|
215
|
+
its(:height? ){ should be_false }
|
216
|
+
its(:weight? ){ should be_false }
|
217
|
+
its(:size? ){ should be_false }
|
218
|
+
its(:iq? ){ should be_true }
|
219
|
+
its(:intelegence? ){ should be_true }
|
220
|
+
its(:name ){ should eq 'jared'}
|
221
|
+
its(:level_of_schooling ){ should eq 100 }
|
222
|
+
its(:height ){ should eq 2 }
|
223
|
+
its(:weight ){ should eq 2 }
|
224
|
+
its(:size ){ should eq 400 }
|
225
|
+
its(:iq ){ should eq 120.0 }
|
226
|
+
its(:intelegence ){ should eq 1.0 }
|
227
|
+
|
228
|
+
its(:keys){ should eq Set[:name, :level_of_schooling, :height, :weight, :size, :iq, :intelegence] }
|
229
|
+
its(:to_hash){ should eq(:name=>"jared", :level_of_schooling=>100, :height=>2, :weight=>2, :size=>400, :iq=>120.0, :intelegence=>1.0) }
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'argument errors' do
|
233
|
+
it "should be raised" do
|
234
|
+
expect{ subject.parse }.to raise_error ArgumentError, 'wrong number of arguments (0 for 1)'
|
235
|
+
expect{ subject.parse({}) }.to raise_error ArgumentError, 'required options: :intelegence, :iq, :level_of_schooling, :name'
|
236
|
+
expect{ subject.parse(intelegence: 1) }.to raise_error ArgumentError, 'required options: :iq, :level_of_schooling, :name'
|
237
|
+
expect{ subject.parse(intelegence: 1, iq: 1) }.to raise_error ArgumentError, 'required options: :level_of_schooling, :name'
|
238
|
+
expect{ subject.parse(intelegence: 1, iq: 1, level_of_schooling: 1) }.to raise_error ArgumentError, 'required options: :name'
|
239
|
+
expect{ subject.parse(intelegence: 1, iq: 1, level_of_schooling: 1, name: 1) }.to_not raise_error
|
240
|
+
expect{ subject.parse(b:1, a:2, name:'steve') }.to raise_error ArgumentError, 'unknown options: :a, :b'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
describe 'Person.new' do
|
246
|
+
let(:proc){ ->{ Person.new } }
|
247
|
+
subject{ proc }
|
248
|
+
it { should raise_error ArgumentError, 'required options: :intelegence, :iq, :level_of_schooling, :name' }
|
249
|
+
|
250
|
+
describe 'ArgumentError' do
|
251
|
+
let(:error){ begin; proc.call; rescue => error; error end }
|
252
|
+
subject{ error }
|
253
|
+
|
254
|
+
describe 'backtrace[0]' do
|
255
|
+
subject{ error.backtrace[0] }
|
256
|
+
it { should eq "#{Bundler.root+'spec/support/person.rb'}:6:in `initialize'" }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe 'Person.new(father:1)' do
|
262
|
+
let(:proc){ ->{ Person.new(father:1) } }
|
263
|
+
subject{ proc }
|
264
|
+
it { should raise_error ArgumentError, 'unknown options: :father' }
|
265
|
+
|
266
|
+
describe 'ArgumentError' do
|
267
|
+
let(:error){ begin; proc.call; rescue => error; error end }
|
268
|
+
subject{ error }
|
269
|
+
|
270
|
+
describe 'backtrace[0]' do
|
271
|
+
subject{ error.backtrace[0] }
|
272
|
+
it { should eq "#{Bundler.root+'spec/support/person.rb'}:6:in `initialize'" }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe %[Person.new(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2)] do
|
278
|
+
subject{ Person.new(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2) }
|
279
|
+
it{ should respond_to :name }
|
280
|
+
it{ should respond_to :level_of_schooling }
|
281
|
+
it{ should respond_to :height }
|
282
|
+
it{ should respond_to :weight }
|
283
|
+
it{ should respond_to :size }
|
284
|
+
it{ should respond_to :iq }
|
285
|
+
it{ should respond_to :intelegence }
|
286
|
+
its(:name ){ should eq 'jared'}
|
287
|
+
its(:level_of_schooling ){ should eq 100 }
|
288
|
+
its(:height ){ should eq 2 }
|
289
|
+
its(:weight ){ should eq 2 }
|
290
|
+
its(:size ){ should eq 400 }
|
291
|
+
its(:iq ){ should eq 120.0 }
|
292
|
+
its(:intelegence ){ should eq 1.0 }
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'options_hash'
|
2
|
+
require 'pry-debugger'
|
3
|
+
|
4
|
+
require Bundler.root + 'spec/support/empty_options.rb'
|
5
|
+
require Bundler.root + 'spec/support/person_options.rb'
|
6
|
+
require Bundler.root + 'spec/support/person.rb'
|
7
|
+
|
8
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
9
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
10
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
11
|
+
# loaded once.
|
12
|
+
#
|
13
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
config.filter_run :focus
|
18
|
+
|
19
|
+
# Run specs in random order to surface order dependencies. If you find an
|
20
|
+
# order dependency and want to debug it, you can fix the order by providing
|
21
|
+
# the seed, which is printed after each run.
|
22
|
+
# --seed 1234
|
23
|
+
config.order = 'random'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: options_hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jared Grippe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-debugger
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A configurable options hash definition
|
70
|
+
email:
|
71
|
+
- jared@deadlyicon.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .travis.yml
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/options_hash.rb
|
84
|
+
- lib/options_hash/version.rb
|
85
|
+
- options_hash.gemspec
|
86
|
+
- spec/options_hash_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
- spec/support/empty_options.rb
|
89
|
+
- spec/support/person.rb
|
90
|
+
- spec/support/person_options.rb
|
91
|
+
homepage: https://github.com/deadlyicon/options_hash
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.1.9
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: A configurable options hash definition
|
115
|
+
test_files:
|
116
|
+
- spec/options_hash_spec.rb
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
- spec/support/empty_options.rb
|
119
|
+
- spec/support/person.rb
|
120
|
+
- spec/support/person_options.rb
|