File Manager
Viewing File: immutable_collections.rb
#--
# Copyright:: Copyright (c) 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_relative "common_api"
require_relative "mixin/state_tracking"
require_relative "mixin/immutablize_array"
require_relative "mixin/immutablize_hash"
class Chef
class Node
module Immutablize
# For elements like Fixnums, true, nil...
def safe_dup(e)
e.dup
rescue TypeError
e
end
def convert_value(value)
case value
when Hash
ImmutableMash.new(value, __root__, __node__, __precedence__)
when Array
ImmutableArray.new(value, __root__, __node__, __precedence__)
when ImmutableMash, ImmutableArray
value
else
safe_dup(value).freeze
end
end
def immutablize(value)
convert_value(value)
end
end
# == ImmutableArray
# ImmutableArray is used to implement Array collections when reading node
# attributes.
#
# ImmutableArray acts like an ordinary Array, except:
# * Methods that mutate the array are overridden to raise an error, making
# the collection more or less immutable.
# * Since this class stores values computed from a parent
# Chef::Node::Attribute's values, it overrides all reader methods to
# detect staleness and raise an error if accessed when stale.
class ImmutableArray < Array
include Immutablize
alias :internal_push :<<
private :internal_push
def initialize(array_data = [])
array_data.each do |value|
internal_push(immutablize(value))
end
end
# For elements like Fixnums, true, nil...
def safe_dup(e)
e.dup
rescue TypeError
e
end
def dup
Array.new(map { |e| safe_dup(e) })
end
def to_a
Array.new(map do |v|
case v
when ImmutableArray
v.to_a
when ImmutableMash
v.to_h
else
safe_dup(v)
end
end)
end
alias_method :to_array, :to_a
# As Psych module, not respecting ImmutableArray object
# So first convert it to Hash/Array then parse it to `.to_yaml`
def to_yaml(*opts)
to_a.to_yaml(*opts)
end
private
# needed for __path__
def convert_key(key)
key
end
prepend Chef::Node::Mixin::StateTracking
prepend Chef::Node::Mixin::ImmutablizeArray
end
# == ImmutableMash
# ImmutableMash implements Hash/Dict behavior for reading values from node
# attributes.
#
# ImmutableMash acts like a Mash (Hash that is indifferent to String or
# Symbol keys), with some important exceptions:
# * Methods that mutate state are overridden to raise an error instead.
# * Methods that read from the collection are overridden so that they check
# if the Chef::Node::Attribute has been modified since an instance of
# this class was generated. An error is raised if the object detects that
# it is stale.
# * Values can be accessed in attr_reader-like fashion via method_missing.
class ImmutableMash < Mash
include Immutablize
include CommonAPI
# this is for deep_merge usage, chef users must never touch this API
# @api private
def internal_set(key, value)
regular_writer(key, convert_value(value))
end
def initialize(mash_data = {})
mash_data.each do |key, value|
internal_set(key, value)
end
end
alias :attribute? :has_key?
# NOTE: #default and #default= are likely to be pretty confusing. For a
# regular ruby Hash, they control what value is returned for, e.g.,
# hash[:no_such_key] #=> hash.default
# Of course, 'default' has a specific meaning in Chef-land
def dup
h = Mash.new
each_pair do |k, v|
h[k] = safe_dup(v)
end
h
end
def to_h
h = {}
each_pair do |k, v|
h[k] =
case v
when ImmutableMash
v.to_h
when ImmutableArray
v.to_a
else
safe_dup(v)
end
end
h
end
alias_method :to_hash, :to_h
# As Psych module, not respecting ImmutableMash object
# So first convert it to Hash/Array then parse it to `.to_yaml`
def to_yaml(*opts)
to_h.to_yaml(*opts)
end
# For elements like Fixnums, true, nil...
def safe_dup(e)
e.dup
rescue TypeError
e
end
prepend Chef::Node::Mixin::StateTracking
prepend Chef::Node::Mixin::ImmutablizeHash
end
end
end