Config merging

This commit is contained in:
Mitchell Hashimoto 2012-04-17 10:22:24 -07:00
parent 92ee042fc2
commit c0ee3b06ff
4 changed files with 129 additions and 10 deletions

View File

@ -10,6 +10,60 @@ module Vagrant
# @param [Proc] config_proc # @param [Proc] config_proc
# @return [Object] # @return [Object]
def self.load(config_proc) def self.load(config_proc)
# Create a root configuration object
root = new_root_object
# Call the proc with the root
config_proc.call(root)
# Return the root object, which doubles as the configuration object
# we actually use for accessing as well.
root
end
# Merges two configuration objects.
#
# @param [V1::Root] old The older root config.
# @param [V1::Root] new The newer root config.
# @return [V1::Root]
def self.merge(old, new)
# Grab the internal states, we use these heavily throughout the process
old_state = old.__internal_state
new_state = new.__internal_state
# The config map for the new object is the old one merged with the
# new one.
config_map = old_state["config_map"].merge(new_state["config_map"])
# Merge the keys.
old_keys = old_state["keys"]
new_keys = new_state["keys"]
keys = {}
old_keys.each do |key, old|
if new_keys.has_key?(key)
# We need to do a merge, which we expect to be available
# on the config class itself.
keys[key] = old.merge(new_keys[key])
else
# We just take the old value, but dup it so that we can modify.
keys[key] = old.dup
end
end
new_keys.each do |key, new|
# Add in the keys that the new class has that we haven't merged.
if !keys.has_key?(key)
keys[key] = new.dup
end
end
# Return the final root object
V1::Root.new(config_map, keys)
end
protected
def self.new_root_object
# Get all the registered plugins # Get all the registered plugins
config_map = {} config_map = {}
Vagrant.plugin("1").registered.each do |plugin| Vagrant.plugin("1").registered.each do |plugin|
@ -19,14 +73,7 @@ module Vagrant
end end
# Create the configuration root object # Create the configuration root object
root = V1::Root.new(config_map) V1::Root.new(config_map)
# Call the proc with the root
config_proc.call(root)
# Return the root object, which doubles as the configuration object
# we actually use for accessing as well.
root
end end
end end
end end

View File

@ -8,8 +8,8 @@ module Vagrant
# configuration classes. # configuration classes.
# #
# @param [Hash] config_map Map of key to config class. # @param [Hash] config_map Map of key to config class.
def initialize(config_map) def initialize(config_map, keys=nil)
@keys = {} @keys = keys || {}
@config_map = config_map @config_map = config_map
end end
@ -29,6 +29,17 @@ module Vagrant
super super
end end
end end
# Returns the internal state of the root object. This is used
# by outside classes when merging, and shouldn't be called directly.
# Note the strange method name is to attempt to avoid any name
# clashes with potential configuration keys.
def __internal_state
{
"config_map" => @config_map,
"keys" => @keys
}
end
end end
end end
end end

View File

@ -17,4 +17,18 @@ describe Vagrant::Config::V1::Root do
instance = described_class.new({}) instance = described_class.new({})
expect { instance.foo }.to raise_error(NoMethodError) expect { instance.foo }.to raise_error(NoMethodError)
end end
it "can be created with initial state" do
instance = described_class.new({}, { :foo => "bar" })
instance.foo.should == "bar"
end
it "should return internal state" do
map = { "foo" => Object, "bar" => Object }
instance = described_class.new(map)
instance.__internal_state.should == {
"config_map" => map,
"keys" => {}
}
end
end end

View File

@ -31,4 +31,51 @@ describe Vagrant::Config::V1 do
end end
end end
end end
describe "merging" do
it "should merge available configuration keys" do
old = Vagrant::Config::V1::Root.new({ :foo => Object })
new = Vagrant::Config::V1::Root.new({ :bar => Object })
result = described_class.merge(old, new)
result.foo.should be_kind_of(Object)
result.bar.should be_kind_of(Object)
end
it "should merge instantiated objects" do
config_class = Class.new do
attr_accessor :value
end
old = Vagrant::Config::V1::Root.new({ :foo => config_class })
old.foo.value = "old"
new = Vagrant::Config::V1::Root.new({ :bar => config_class })
new.bar.value = "new"
result = described_class.merge(old, new)
result.foo.value.should == "old"
result.bar.value.should == "new"
end
it "should merge conflicting classes by calling `merge`" do
config_class = Class.new do
attr_accessor :value
def merge(new)
result = self.class.new
result.value = @value + new.value
result
end
end
old = Vagrant::Config::V1::Root.new({ :foo => config_class })
old.foo.value = 10
new = Vagrant::Config::V1::Root.new({ :foo => config_class })
new.foo.value = 15
result = described_class.merge(old, new)
result.foo.value.should == 25
end
end
end end