diff options
author | John Keiser <john@johnkeiser.com> | 2016-09-16 15:55:18 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2016-09-16 18:14:05 -0700 |
commit | 9ff31d777d7d80029118e04da743dfed505365b8 (patch) | |
tree | 12df2409d242d7869a4188b7f19850748bc570db | |
parent | c30c3fb4003bdf3a4f5b44f28d0dcb9aeae89fa6 (diff) | |
download | chef-9ff31d777d7d80029118e04da743dfed505365b8.tar.gz |
Add ArrayProperty and Boolean property types.
Also adds a `transform` method on Property to allow properties to be used to simply coerce values.
-rw-r--r-- | lib/chef/mixin/properties.rb | 8 | ||||
-rw-r--r-- | lib/chef/property.rb | 10 | ||||
-rw-r--r-- | lib/chef/property/array_property.rb | 151 | ||||
-rw-r--r-- | spec/unit/property/validation_spec.rb | 10 | ||||
-rw-r--r-- | spec/unit/property_spec.rb | 33 |
5 files changed, 211 insertions, 1 deletions
diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb index 8ff2cc4501..afda0fef36 100644 --- a/lib/chef/mixin/properties.rb +++ b/lib/chef/mixin/properties.rb @@ -301,6 +301,14 @@ class Chef raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property property.reset(self) end + + # Same as property_type above, just used to declare types inline here. + def self.property_type(**options) + Property.derive(**options) + end + Boolean = property_type is: [ true, false ], coerce: proc { |v| !!v } + require "chef/property/array_property" + ArrayProperty = Chef::Property::ArrayProperty end end end diff --git a/lib/chef/property.rb b/lib/chef/property.rb index a357ba9ee3..08039a6ece 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -630,7 +630,15 @@ class Chef end end - private + # + # De-lazify, coerce and validate the given value without storing it. + # + def transform(resource, value) + value = input_to_stored_value(resource, value) + stored_value_to_output(resource, value) + end + + protected def exec_in_resource(resource, proc, *args) if resource diff --git a/lib/chef/property/array_property.rb b/lib/chef/property/array_property.rb new file mode 100644 index 0000000000..35858e3d83 --- /dev/null +++ b/lib/chef/property/array_property.rb @@ -0,0 +1,151 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright 2016-2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/property" + +class Chef + class Property + # + # Property allowing a freeform array setter syntax: + # + # ``` + # my_property 1, 2, 3 + # # ==> [ 1, 2, 3 ] + # ``` + # + # Also allows for an element type, and coerces and validates using said type. + # + class ArrayProperty < Property + # + # Creates a new array property type. + # + # Has the same arguments as Property, plus: + # + # @option options [Property] :element_type The element type of the property. + # @option options [Property] :append `true` to have each call to + # `my_property x` append x to the array value. `my_property = x` will + # still wipe out any existing values. Defaults to `false`. + # + def initialize(**options) + super + end + + # + # The element type. + # + # @return [Property] The element type of the property. + # + def element_type + options[:element_type] + end + + # + # Whether to append to the array or set it. + # + # @return [Boolean] `true` if calls to `my_property x` will append x to the array; + # `false` if it will set the array to `[ x ]` + # + def append? + options[:append] + end + + # + # Creates a new array property type with the given element type. + # + # @param element_type [Property] The element type of the array. + # @param options [Hash] Other options to the property type (same as options to `property_type`). + # + # @return [ArrayProperty] The array property type. + # + # @example + # ArrayProperty[String, regex: /abc/] = array of strings matching abc + # + # @example + # ArrayProperty[String, Integer] = array of either strings or integers + # + # @example + # ArrayProperty[regex: /abc/] = array of strings matching abc + # + def self.[](*element_type, **options) + element_type = element_type[0] if element_type.size == 1 + case element_type + when [] + element_type = nil + when Property + element_type = element_type.derive(**options) if options.any? + else + options[:is] = element_type + element_type = Property.derive(**options) + end + ArrayProperty.new(element_type: element_type) + end + + # allows syntax "myproperty :a, :b, :c" == `myproperty [ :a, :b, :c ]` + def call(resource, *values) + # myproperty nil should work the same way it currently does. + case values.size + # myproperty with no arguments does a get + when 0 + return super + when 1 + value = values[0] + # myproperty nil behaves as normal + return super if value.nil? + + # myproperty [ :a, :b, :c ] sets to [ :a, :b, :c ] + # myproperty :a sets to [ :a ] + value = Array(value) + + else + # myproperty :a, :b, :c sets to [ :a, :b, :c ] + value = values + end + + # If we are appending, and there are values to append, do so + if append? && value.is_a?(Enumerable) + return append(resource, value) + end + + super(resource, value) + end + + # coerces non-arrays to arrays; coerces array elements using element_type.coerce + def coerce(resource, value) + value = Array(value) + value = value.map { |element| element_type.coerce(resource, element) } if element_type + super(resource, value) + end + + # validates the elements of the array using element_type.validate + def validate(resource, value) + super + value.each { |element| element_type.validate(resource, element) } if element_type + end + + private + + def append(resource, values) + values = input_to_stored_value(resource, values) + if is_set?(resource) + values = get_value(resource) + values + end + set_value(resource, values) + end + end + end +end diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb index 4e1b252863..a583c121c5 100644 --- a/spec/unit/property/validation_spec.rb +++ b/spec/unit/property/validation_spec.rb @@ -284,6 +284,16 @@ describe "Chef::Resource.property validation" do [ nil, "thing" ], [ :nope, false ], :nillable + + validation_test "Boolean", + [ true, false ], + [ 1, "true" ], + :nil_is_valid + + validation_test "ArrayProperty", + [ 1, [], [1,2,3] ], + [ ], + :nil_is_valid end # is diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 50ff3434f6..8f2a67c756 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -1224,4 +1224,37 @@ describe "Chef::Resource.property" do end end end + + # ArrayProperty tests + with_property ":x, ArrayProperty" do + it "x 1 sets x to [1]" do + expect(resource.x 1).to eq [1] + expect(resource.x).to eq [1] + end + it "x 1, 2, 3 sets x to [1,2,3]" do + expect(resource.x 1, 2, 3).to eq [1,2,3] + expect(resource.x).to eq [1,2,3] + end + it "x [1] sets x to [1]" do + expect(resource.x [1]).to eq [1] + expect(resource.x).to eq [1] + end + it "x [] sets x to []" do + expect(resource.x []).to eq [] + expect(resource.x).to eq [] + end + + it "x [[1]] sets x to [[1]]" do + expect(resource.x [[1]]).to eq [[1]] + expect(resource.x).to eq [[1]] + end + it "x [[]] sets x to [[]]" do + expect(resource.x [[]]).to eq [[]] + expect(resource.x).to eq [] + end + it "x [1], [2] sets x to [[1], [2]]" do + expect(resource.x [[1], [2]]).to eq [[1], [2]] + expect(resource.x).to eq [[1], [2]] + end + end end |