cushion_defaults 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a81419a81ff0cdcd46f6c56967342aa8c0663853
4
+ data.tar.gz: 32731179906c9bbe94c7e56a1e5716409214c548
5
+ SHA512:
6
+ metadata.gz: 5f2c6ef61d48aa83e0ff9723c68e807a159926c5a74e10109b1f0354a9d4b877f7525b6f518add68e7668d0eb01f8d95c9e480fb434d01634b310defab80e180
7
+ data.tar.gz: 364a9f1df425222db45df3ee27c703ecb8401a4a075c53bb8e648ec8526fc7452f7ae8955c5437a87250cb04e1680775b753803d5388dacb4688c9996e3b64bd
@@ -0,0 +1,189 @@
1
+ module CushionDefaults
2
+ # A series of class methods to be plopped into any class that includes CushionDefaults.
3
+ module ClassMethods
4
+
5
+ # Getter-method for the +defaults+ +DefaultsHash+.
6
+ attr_reader :defaults
7
+
8
+ # Either set up or wipe @defaults. Should not usually be called directly.
9
+ def initialize_defaults_hash
10
+
11
+ if @defaults
12
+ # We need to maintain the identity of the hash, as child classes may have stored a reference to it
13
+ @defaults.delete_if { true }
14
+ else
15
+ @defaults = DefaultsHash.new(self)
16
+ end
17
+ end
18
+
19
+ # Wipe @defaults and replace it with the keys/vals of +replacement_hash+.
20
+ #
21
+ # If you only want to add one or more defaults, then write instead self.defaults += {new_key: val}.
22
+ #
23
+ # Note that all keys are coerced into +Symbols+.
24
+ # +replacement_hash+ :: a hash (of any length) whose keys/values are to be replace those in +defaults+.
25
+ def defaults=(replacement_hash)
26
+ # Need to copy over keys/vals to ensure @defaults remains a DefaultsHash and retains identity
27
+
28
+ @defaults.replace(replacement_hash)
29
+ @defaults.keys.each do |key|
30
+ unless key.is_a? Symbol
31
+ @defaults[key.to_sym] = @defaults[key].delete!
32
+ end
33
+ end
34
+ end
35
+
36
+ # Convenience method equivalent to +@defaults[key] = val+
37
+ # +key+ :: Key for new default. Is coerced into a +Symbol+.
38
+ # +val+ :: Default value for +key+.
39
+ def set_default(key, val)
40
+ @defaults[key.to_sym] = val
41
+ end
42
+
43
+ # Load in the defaults for this class from a yaml file.
44
+ # If +source_path+ is specified, yaml file is loaded from there.
45
+ # Otherwise, *CushionDefaults::YAML_PATH* is evaluated for the current class.
46
+ # By default, the yaml file for +Klass+ is expected to be at +config/klass.yaml+.
47
+ # +source_path+ :: File path to the yaml configuration file for this class
48
+ def defaults_from_yaml(source_path = nil)
49
+ if source_path
50
+ yaml_path = "#{CushionDefaults::configuration.yaml_source_full_path}#{source_path}.yaml"
51
+ else
52
+ yaml_path = CushionDefaults.conf.yaml_file_for(self)
53
+ end
54
+
55
+ yaml = YAML::load(File.open(yaml_path)) rescue {}
56
+
57
+ if yaml.empty? && CushionDefaults::configuration.whiny_yaml
58
+ warn "No YAML configuration for class #{self} found at #{yaml_path}"
59
+ end
60
+
61
+ initialize_defaults_hash
62
+ # If automatic readers and writers are enabled, this will set them up as a consequence.
63
+ yaml.each do |key, val|
64
+ @defaults[key.to_sym] = val
65
+ end
66
+ end
67
+
68
+ # Sets up a conditional getter method for each :sym in +syms+.
69
+ #
70
+ # Each getter method checks if its instance variable (:sym) is defined. If it is, it returns that. If not, it
71
+ # returns the default value.
72
+ #
73
+ # The getters are named according to the same format as +attr_reader+.
74
+ # +syms+ :: One or more +Symbol+s representing those instance variables that should have +conditional_getters+
75
+ def cushion_reader(*syms)
76
+ syms.each do |sym|
77
+ sym = sym.to_sym
78
+ sym_str = sym.to_s
79
+ if self_or_parent_instance_method?(sym)
80
+ warn "#{self} or a parent class already has what looks like a getter method for #{sym_str}"
81
+ end
82
+ define_method(sym) do
83
+ instance_variable_string = "@#{sym_str}"
84
+ if defaults.not_pushy?(sym) && instance_variable_defined?(instance_variable_string)
85
+ instance_variable_get(instance_variable_string)
86
+ else
87
+ defaults[sym]
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def cushion_writer(*syms)
94
+ syms.each do |sym|
95
+ method_name = "#{sym}="
96
+ if self_or_parent_instance_method?(method_name)
97
+ warn "#{self} or a parent class already has what looks like a setter method for #{sym}"
98
+ end
99
+
100
+ instance_variable_string = "@#{sym}"
101
+
102
+ define_method(method_name) do |y|
103
+ if CushionDefaults.nilish? y
104
+ if CushionDefaults.conf.whiny_ignores
105
+ warn "You are attempting to set a nilish value for #{sym}. This will not be recorded, and any value set will be deleted."
106
+ end
107
+ remove_instance_variable(instance_variable_string) if instance_variable_defined?(instance_variable_string)
108
+ else
109
+ if defaults.pushy?(sym)
110
+ warn "You are setting a value for #{sym}, but this is a pushy default and this value will not be returned by any cushion_readers."
111
+ end
112
+ instance_variable_set(instance_variable_string, y)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def remove_reader(sym)
119
+ undef_method(sym) if self_has_method?(sym)
120
+ end
121
+
122
+ def remove_writer(sym)
123
+ write_sym = "#{sym}=".to_sym
124
+ undef_method(write_sym) if self_has_method?(write_sym)
125
+ end
126
+
127
+ # Sets up both +conditional_getters+ and normal +attr_writer+ for +syms+.
128
+ # +syms+ :: One or more symbols representing those instance variables that should have +conditional_getters+ and normal +attr_writers+.
129
+ def cushion(*syms)
130
+ cushion_reader(*syms)
131
+
132
+ cushion_writer(*syms)
133
+ end
134
+
135
+ # Defines +conditional_getters+ for all of this class' +defaults+.
136
+ #
137
+ # Only defines +conditional_getters+ for defaults for this class. All other getters are assumed to have been defined
138
+ # further up the class tree.
139
+ #
140
+ # Thus, if class +A+ defines the default +var1+, and class +B+ defines the default +var2+, calling this method
141
+ # within class +B+ will only generate a getter for +var2+.
142
+ def cushion_readers_for_defaults
143
+ cushion_reader *defaults.keys
144
+ end
145
+
146
+ # Defines +conditional_getters+ and +attr_writers+ for all of this class' +defaults+.
147
+ #
148
+ # Only defines +conditional_getters+ for this class defaults. All other getters are assumed to have been defined
149
+ # further up the class tree.
150
+ #
151
+ # Thus, if class +A+ defines the default +var1+, and class +B+ defines the default +var2+, calling this method
152
+ # within class +B+ will only generate a getter for +var2+.
153
+ #
154
+ # See also:
155
+ # - #cushion
156
+ # - #cushion_readers_for_defaults
157
+ def cushion_defaults
158
+ cushion *defaults.keys
159
+ end
160
+
161
+ def make_pushy(*syms)
162
+ syms.each { |sym| @defaults.pushy!(sym) }
163
+ end
164
+
165
+ def make_polite(*syms)
166
+ syms.each { |sym| @defaults.not_pushy!(sym) }
167
+ end
168
+
169
+ # Ensure that if class +Klass+ includes +CushionDefaults+, then any class that subclasses +Klass+ will include it as
170
+ # well.
171
+ #
172
+ # Called automatically whenever any class that includes +CushionDefaults+ is inherited.
173
+ def inherited(inheritor)
174
+ inheritor.send :include, CushionDefaults
175
+ end
176
+
177
+ protected
178
+
179
+ # Check whether this class or any parent class includes the instance method denoted by +sym+
180
+ # +sym+ :: Symbol representing the method in question
181
+ def self_or_parent_instance_method?(sym)
182
+ instance_methods(true).include?(sym.to_sym)
183
+ end
184
+
185
+ def self_has_method?(sym)
186
+ instance_methods(false).include?(sym.to_sym)
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,56 @@
1
+ # Effectively a singleton class, +Configuration+ keeps track of various configuration options for +CushionDefaults+.
2
+ class Configuration # :nodoc:
3
+ # adapted from http://brandonhilkert.com/blog/ruby-gem-configuration-patterns/
4
+
5
+ attr_accessor :update_readers, :update_writers, :auto_load_from_yaml, :yaml_source_folder, :yaml_source_full_path, :whiny_yaml, :whiny_ignores, :cushion_child_defaults_in_parent, :ignore_attempts_to_set_nil, :blank_str_is_nil
6
+
7
+ # Initializes with +#defaults+
8
+ def initialize
9
+ defaults!
10
+ end
11
+
12
+ # Returns or computes the folder where class-specific yaml files are expected to reside.
13
+ def yaml_source_full_path
14
+ @yaml_source_full_path || CushionDefaults::CALLING_PATH + yaml_source_folder
15
+ end
16
+
17
+ # Update configuration options with those values contained within +loaded_config+.
18
+ #
19
+ # +loaded_config+ :: hash from which options will be derived
20
+ def from_hash(loaded_config)
21
+ loaded_config.each do |key, val|
22
+ instance_variable_set(key.to_sym, val) if instance_variable_defined? key.to_sym
23
+ end
24
+ end
25
+
26
+ # Defines default configuration settings
27
+ def defaults!
28
+ self.update_readers = false
29
+ self.update_writers = false
30
+ self.auto_load_from_yaml = true
31
+ self.yaml_source_folder = 'config/cushion_defaults/'
32
+ self.yaml_source_full_path = nil
33
+ self.whiny_yaml = false
34
+ self.whiny_ignores = false
35
+ self.ignore_attempts_to_set_nil = true
36
+ self.blank_str_is_nil = true
37
+ end
38
+
39
+ def test_settings!
40
+ defaults!
41
+ self.whiny_yaml = true
42
+ self.whiny_ignores = true
43
+ end
44
+
45
+ def underscore(s)
46
+ s.gsub(/::/, '/').
47
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
48
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
49
+ tr("-", "_").
50
+ downcase
51
+ end
52
+
53
+ def yaml_file_for(klass)
54
+ "#{CushionDefaults::configuration.yaml_source_full_path}#{underscore(klass.to_s)+'.yaml'}"
55
+ end
56
+ end
@@ -0,0 +1,165 @@
1
+ module CushionDefaults
2
+ # Slight expansion of +Hash+.
3
+ #
4
+ # Records a link to +owner+ and will traverse up the chain of owners looking for a default value for +x+ if no default
5
+ # value for +x+ is specified.
6
+ class DefaultsHash < Hash
7
+ # +default_proc+ does most of the work.
8
+
9
+ # Must be initialized with an +owner+ class.
10
+
11
+ attr_reader :pushy_defaults, :polite_defaults
12
+
13
+ # +owner+ :: Class for which this +DefaultsHash+ contains defaults.
14
+ def initialize(owner)
15
+ self.default_proc = proc { |_hash, key| super_defaults[key] }
16
+ # look in the superclass' +defaults+ hash, if a key is not found in this class' +defaults+ hash
17
+ @owner = owner
18
+ @pushy_defaults = Set.new
19
+ @polite_defaults = Set.new
20
+ end
21
+
22
+ # Allow addition of hashes. Works as expected.
23
+ #
24
+ # Note that this also enables +=.
25
+ #
26
+ # If +key+ is in +self+ and +y+, then the value of +y+ will replace that in +self+ in the resulting +Hash+.
27
+ # +y+ :: Hash (of any length) whose key/values will be added to this +DefaultsHash+.
28
+ def +(y)
29
+ y.each {|key, val| self[key] = val}
30
+ self
31
+ end
32
+
33
+ def add_methods_as_needed(key)
34
+ @owner.cushion_reader key.to_sym if CushionDefaults.conf.update_readers
35
+ @owner.cushion_writer key.to_sym if CushionDefaults.conf.update_writers
36
+ end
37
+
38
+ def remove_methods_as_needed(key)
39
+ @owner.remove_reader key.to_sym if CushionDefaults.conf.update_readers
40
+ @owner.remove_writer key.to_sym if CushionDefaults.conf.update_writers
41
+ end
42
+
43
+ # Custom key/value set method. Prevents writing a default when a parent has it marked as pushy (and it is not
44
+ # otherwise marked as polite), and it also tells @owner to add methods as needed (if writers or readers are to be
45
+ # automatically added).
46
+ def []=(key,val)
47
+ key = key.to_sym
48
+ if !@polite_defaults.include?(key) && pushy_in_parent?(key)
49
+ raise ArgumentError, 'You cannot set a default value marked as pushy in a parent class without first marking it as polite.'
50
+ end
51
+ unless has_ish_key?(key)
52
+ add_methods_as_needed(key)
53
+ end
54
+ super(key, val)
55
+ end
56
+
57
+ # Custom delete method. We need to check whether or not we should delete the methods associated with the key, and we
58
+ # need to remove the key from @pushy_defaults and @polite_defaults if it exists.
59
+ def delete(key)
60
+ key = key.to_sym
61
+ remove_methods_as_needed(key)
62
+ @pushy_defaults.delete(key)
63
+ @polite_defaults.delete(key)
64
+ super(key)
65
+ end
66
+
67
+ # Custom clear method. We need to check whether or not we should delete the methods associated with the keys, and we
68
+ # need to wipe @pushy_defaults and @polite_defaults.
69
+ def clear
70
+ keys.each do |key|
71
+ remove_methods_as_needed(key.to_sym)
72
+ end
73
+ @pushy_defaults.clear
74
+ @polite_defaults.clear
75
+ super
76
+ end
77
+
78
+ # Custom clear method. We need to check whether or not we should delete the methods associated with the key.
79
+
80
+ # Determine if this +DefaultsHash+ "ish" has a key. In other words, whether it or any Hashes up the chain of
81
+ # +super_defaults+ has the key +key+.
82
+ #
83
+ # See also:
84
+ # - #ish_keys
85
+ def has_ish_key?(key)
86
+ # Obviously, this method gives much better performance than calling +ish_keys.include?(key)+.
87
+ if has_key?(key)
88
+ true
89
+ elsif !super_defaults.empty?
90
+ # super_defaults could theoretically be either a DefaultsHash or a regular Hash
91
+ if super_defaults.respond_to? :has_ish_key?
92
+ super_defaults.has_ish_key?(key)
93
+ else
94
+ super_defaults.has_key?(key)
95
+ end
96
+ else
97
+ false
98
+ end
99
+ end
100
+
101
+ # Returns the keys of this class' +defaults+ as well as those of parent classes (if extant).
102
+ #
103
+ # See also:
104
+ # - #has_ish_key?
105
+ def ish_keys
106
+ if super_defaults
107
+ # super_defaults could be either a DefaultsHash or a regular Hash
108
+ (keys + (super_defaults.respond_to?(:ish_keys) ? super_defaults.ish_keys : super_defaults.keys)).uniq
109
+ else
110
+ keys
111
+ end
112
+ end
113
+
114
+ def pushy!(sym)
115
+ @pushy_defaults.add(sym)
116
+ @polite_defaults.delete(sym)
117
+ end
118
+
119
+ def not_pushy!(sym)
120
+ @pushy_defaults.delete(sym)
121
+ @polite_defaults.add(sym)
122
+ end
123
+
124
+ def pushy?(sym)
125
+ if @polite_defaults.include?(sym)
126
+ # white list has priority
127
+ false
128
+ elsif @pushy_defaults.include?(sym)
129
+ true
130
+ else
131
+ pushy_in_parent?(sym)
132
+ end
133
+ end
134
+
135
+ def pushy_in_parent?(sym)
136
+ if super_defaults.respond_to? :pushy?
137
+ super_defaults.pushy?(sym)
138
+ else
139
+ false
140
+ end
141
+ end
142
+
143
+ def not_pushy?(sym)
144
+ !pushy?(sym)
145
+ end
146
+
147
+
148
+ def where_is_that_default_again(sym)
149
+ if has_key? sym
150
+ @owner
151
+ elsif super_defaults.respond_to? :where_is_that_default_again
152
+ super_defaults.where_is_that_default_again sym
153
+ else
154
+ nil
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ # Return (unless cached) a reference to the superclass' +defaults+ hash.
161
+ def super_defaults
162
+ @super_defaults ||= @owner.superclass.respond_to?(:defaults) ? @owner.superclass.defaults : {}
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,120 @@
1
+ require 'YAML' # :nodoc:
2
+ require 'set' # :nodoc:
3
+ require 'cushion_defaults/configuration'
4
+ require 'cushion_defaults/class_methods'
5
+ require 'cushion_defaults/defaults_hash'
6
+
7
+ # Base module. Should be included in any class that needs the functionality offered by +CushionDefaults+.
8
+ #
9
+ # When included in a superclass +Klass+, it automatically includes itself in all subclassses that subsequently extend
10
+ # +Klass+.
11
+ module CushionDefaults
12
+
13
+ VERSION = '0.0.0'
14
+
15
+ # CONFIG
16
+
17
+ # Location CushionDefaults looks for (optional) config file
18
+ CONFIG_LOCATION = 'config/cushion_defaults.yaml'
19
+
20
+ CALLING_PATH = File.expand_path(File.dirname($0)) + '/'
21
+
22
+ # If set to true, will automatically look at +YAML_PATH+ for each class that includes +CushionDefaults+ to try to
23
+ # load defaults from yaml file located at the result of +YAML_PROC+. By default, this would be 'config/klass.yaml'.
24
+
25
+ # Relative folder path to yaml file used both automatically if +AUTO_LOAD_FROM_YAML+ and with unparam'd manual calls
26
+ # to +#defaults_from_yaml+.
27
+
28
+ # If true, will complain (i.e., throw a warning) if a yaml file cannot be found for a class, whether called because of
29
+ # AUTO_LOAD_FROM_YAML or via a manual call to +#defaults_from_yaml+.
30
+
31
+ # Create or return a cached (singleton) +Configuration+ object.
32
+ def self.configuration
33
+ @configuration ||= Configuration.new
34
+ end
35
+
36
+ def self.conf
37
+ self.configuration
38
+ end
39
+
40
+ def self.nilish?(y)
41
+ if conf.ignore_attempts_to_set_nil
42
+ y.nil? || (conf.blank_str_is_nil && y.eql?(''))
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ # Yield the configuration object to a block.
49
+ #
50
+ # The following configuration methods are available with the returned object:
51
+ # - #maintain_getters (boolean) :: If true, cushion_getters will be added/removed automatically as defaults are added/removed.
52
+ # - #maintain_writers (boolean) :: If true, cushion_setters will be added/removed automatically as defaults are added/removed.
53
+ # - #auto_load_from_yaml (boolean) :: Specifies whether we should attempt to automatically load defaults from class-specific yaml files. Def: true.
54
+ # - #yaml_source_folder (string) :: Specifies the folder that contains class-specific yaml files. Def: 'config/cushion_defaults/'.
55
+ # - #yaml_source_full_path (string) :: Specifies the full path for class-specific yaml files. Def: +nil+.
56
+ # - #whiny_yaml (boolean) :: If true, a warning will be issued if a class-specific yaml file cannot be found. Def: false.
57
+ # - #whiny_ignores (boolean) :: If true, a warning will be issued when a variable is not set (or is undefined) due to being nilish. Def: false.
58
+ # - #cushion_child_defaults_in_parent (boolean) :: If true, then if SubKlass < Klass and getters/setters are added for a default specified in Subklass, then a getter that returns nil will be added to Klass as well. Def: false.
59
+ # - #ignore_attempts_to_set_nil (boolean) :: If true, then if a cushion_writer has been added for :x, then calling var.x = nil will instead remove_instance_variable(x) on var, effectively making it so that var.x will return the default value for x. Def: true.
60
+ # - #blank_str_is_nil (boolean) :: If true (and if #ignore_attempts_to_set_nil), the setter works like Rail's #blank method: if it isn't blank, the instance variable is set. Def: true.
61
+ def self.configure
62
+ yield(configuration)
63
+ end
64
+
65
+ if File.exists?(CONFIG_LOCATION)
66
+ t = File.open(CONFIG_LOCATION)
67
+ elsif File.exists?(configuration.yaml_source_folder + 'cushion_defaults.yaml')
68
+ t = File.open(configuration.yaml_source_folder + 'cushion_defaults.yaml')
69
+ else
70
+ t = nil
71
+ end
72
+
73
+ config.from_hash(YAML::load(t)) if t
74
+
75
+ # Add class methods and set up +DefaultsHash+ when the module is included in a class.
76
+ #
77
+ # If +AUTO_LOAD_FROM_YAML+, then it also searches for a yaml configuration file for the class in the path specified
78
+ # in the config variables.
79
+ def self.included(base)
80
+
81
+ base.extend(ClassMethods)
82
+
83
+ if configuration.auto_load_from_yaml
84
+ base.defaults_from_yaml
85
+ else
86
+ base.initialize_defaults_hash
87
+ end
88
+ end
89
+
90
+ # Convenience method. If +x+ is a +Klass+, then +x#defaults+ returns +Klass.defaults+.
91
+ def defaults
92
+ self.class.defaults
93
+ end
94
+
95
+ # Convenience method. Returns the default value for :sym, going up the chain of cascaded defaults.
96
+ # *sym* :: Symbol representing the variable whose default value is desired.
97
+ def default(sym)
98
+ defaults[sym]
99
+ end
100
+
101
+ # Purely a convenience method.
102
+ def has_specified?(sym)
103
+ instance_variable_defined?("@#{sym}")
104
+ end
105
+
106
+ # 'Crystallize' the default: i.e., if this instance does not have +sym+ specified, then set the value of +sym+
107
+ # explicitly to the default value.
108
+ #
109
+ # This is most useful if you want to update the default value for +sym+ but do not want to affect one or more already
110
+ # exsiting instances of the class.
111
+ def crystallize_default(key, act_if_nilish=true)
112
+ key = key.to_sym
113
+ # crystallize if either (1) there is no value specified for :key, or (2) there is a value specified, but we are
114
+ # acting if a nilish value is specified and the value for :key is nilish.
115
+ if !has_specified? key || (act_if_nilish && CushionDefaults.nilish?(instance_variable_get("@#{key}")))
116
+ default_value = default(key)
117
+ instance_variable_set("@#{key}", default_value)
118
+ end
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cushion_defaults
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Mitchell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Allows you to specify a 'cushion' for various instance variables—effectively
14
+ a default value—that will be returned by optional reader methods if the instance
15
+ variable is undefined.
16
+ email: posgarou@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/cushion_defaults.rb
22
+ - lib/cushion_defaults/class_methods.rb
23
+ - lib/cushion_defaults/configuration.rb
24
+ - lib/cushion_defaults/defaults_hash.rb
25
+ homepage: http://rubygems.org/gems/cushion_defaults
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.4.3
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: An easy, flexible, and powerful alternative to hashes of defaults. Can be
49
+ used both in individual classes and in complex class hierarchies.
50
+ test_files: []