summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2016-09-16 15:55:18 -0700
committerJohn Keiser <john@johnkeiser.com>2016-09-16 18:14:05 -0700
commit9ff31d777d7d80029118e04da743dfed505365b8 (patch)
tree12df2409d242d7869a4188b7f19850748bc570db
parentc30c3fb4003bdf3a4f5b44f28d0dcb9aeae89fa6 (diff)
downloadchef-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.rb8
-rw-r--r--lib/chef/property.rb10
-rw-r--r--lib/chef/property/array_property.rb151
-rw-r--r--spec/unit/property/validation_spec.rb10
-rw-r--r--spec/unit/property_spec.rb33
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