diff options
author | John Keiser <john@johnkeiser.com> | 2015-06-30 14:50:58 -0600 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-07-03 13:10:23 -0600 |
commit | 17039741776d3e597776cee859746d8efd586a6d (patch) | |
tree | 06c1a4423f22a2da3cb24954dd4c3bdfa3922a24 /lib/chef/property.rb | |
parent | 901f122c5a906b8f64e8ed1919674ffe9d839bf0 (diff) | |
download | chef-17039741776d3e597776cee859746d8efd586a6d.tar.gz |
Add Property.create and Resource.property_type for type system overriding
Diffstat (limited to 'lib/chef/property.rb')
-rw-r--r-- | lib/chef/property.rb | 92 |
1 files changed, 83 insertions, 9 deletions
diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 251789f49f..d832a36f85 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -36,6 +36,47 @@ class Chef # class Property # + # Create a reusable property type that can be used in multiple properties + # in different resources. + # + # @param type [Object,Array<Object>] The type(s) of this property. + # If present, this is prepended to the `is` validation option. + # If this is a Chef::Property, `specialize` is called on it to create the + # new property instead of prepending to `is`. + # @param options [Hash<Symbol,Object>] Validation options. see #property for + # the list of options. + # + # @example Bare property_type + # property_type() + # + # @example With just a type + # property_type(String) + # + # @example With just options + # property_type(default: 'hi') + # + # @example With type and options + # property_type(String, default: 'hi') + # + def self.create(type=NOT_PASSED, **options) + # Inherit from the property type, if one is passed + if type.is_a?(self) + type.specialize(**options) + + else + if type != NOT_PASSED + # If a type was passed, combine it with "is" (if a type was passed) + if options[:is] + options[:is] = ([ type ] + [ options[:is] ]).flatten(1) + else + options[:is] = type + end + end + new(**options) + end + end + + # # Create a new property. # # @param options [Hash<Symbol,Object>] Property options, including @@ -210,20 +251,17 @@ class Chef # resource.myprop value # set # ``` # - # If multiple values or a block are passed, they will be passed to coerce. - # If there is no coerce method and multiple values are passed, we will throw - # an error. + # Subclasses may implement this with any arguments they want, as long as + # the corresponding DSL calls it correctly. # # @param resource [Chef::Resource] The resource to get the property from. - # @param value The value to set the property to. If not passed or set to - # NOT_PASSED, this is treated as a get. + # @param value The value to set (or NOT_PASSED if it is a get). # # @return The current value of the property. If it is a `set`, lazy values # will be returned without running, validating or coercing. If it is a # `get`, the non-lazy, coerced, validated value will always be returned. # def call(resource, value=NOT_PASSED) - # myprop with no args if value == NOT_PASSED return get(resource) end @@ -273,8 +311,6 @@ class Chef value else - raise Chef::Exceptions::ValidationFailed, "#{name} is required" if required? - if has_default? value = default if value.is_a?(DelayedEvaluator) @@ -293,6 +329,9 @@ class Chef end value + + elsif required? + raise Chef::Exceptions::ValidationFailed, "#{name} is required" end end end @@ -402,11 +441,46 @@ class Chef # @return [Property] The new property type. # def specialize(**modified_options) - Property.new(**options, **modified_options) + Property.new(**options.merge(**modified_options)) + end + + # + # Emit the DSL for this property into the resource class (`declared_in`). + # + # Creates a getter and setter for the property. + # + def emit_dsl + # We don't create the getter/setter if it's a custom property; we will + # be using the existing getter/setter to manipulate it instead. + return if !instance_variable_name + + # We prefer this form because the property name won't show up in the + # stack trace if you use `define_method`. + declared_in.class_eval <<-EOM, __FILE__, __LINE__+1 + def #{name}(value=NOT_PASSED) + self.class.properties[#{name.inspect}].call(self, value) + end + def #{name}=(value) + self.class.properties[#{name.inspect}].set(self, value) + end + EOM + rescue SyntaxError + # If the name is not a valid ruby name, we use define_method. + resource_class.define_method(name) do |value=NOT_PASSED| + self.class.properties[name].call(self, value) + end + resource_class.define_method("#{name}=") do |value| + self.class.properties[name].set(self, value) + end end protected + # + # The options this Property will use for get/set behavior and validation. + # + # @see #initialize for a list of valid options. + # attr_reader :options # |