class Build::Environment

This is the basic environment data structure which is essentially a linked list of hashes. It is primarily used for organising build configurations across a wide range of different sub-systems, e.g. platform configuration, target configuration, local project configuration, etc.

def self.combine(*environments)

Flatten the list of environments.
def self.combine(*environments)
	ordered = []
	
	environments.each do |environment|
		environment.flatten_to_array(ordered)
	end
	
	ordered.inject(nil) do |parent, environment|
		environment.dup(parent: parent)
	end
end

def self.system_environment(env = ENV, **options)

Construct an environment from a given system environment:
def self.system_environment(env = ENV, **options)
	self.new(nil, Hash[env.map{|key, value| [key.downcase.to_sym, value]}], **options)
end

def == other

def == other
	self.equal?(other) or
		self.class == other.class and
		@parent == other.parent and
		@values == other.values and
		@update == other.update and
		@name == other.name
end

def [](key)

def [](key)
	environment = lookup(key)
	
	environment ? environment.values[key] : nil
end

def []=(key, value)

def []=(key, value)
	@values[key] = value
end

def checksum(digester: Digest::SHA1.new)

def checksum(digester: Digest::SHA1.new)
	checksum_recursively(digester)
	
	return digester.hexdigest
end

def checksum_recursively(digester)

def checksum_recursively(digester)
	sorted_keys.each do |key|
		digester.update(key.to_s)
		
		case value = @values[key]
		when Proc
			digester.update(value.source_location.join)
		else
			digester.update(value.to_s)
		end
	end
	
	@parent.checksum_recursively(digester) if @parent
end

def construct!(proxy, *arguments, &block)

def construct!(proxy, *arguments, &block)
	constructor = Constructor.new(self, proxy)
	
	if block_given?
		constructor.instance_exec(*arguments, &block)
	end
	
	return self
end

def defined

def defined
	@values.select{|name,value| Define === value}
end

def dup(parent: @parent, values: @values, update: @update, name: @name)

def dup(parent: @parent, values: @values, update: @update, name: @name)
	self.class.new(parent, values.dup, name: name, &update)
end

def eql?(other)

def eql?(other)
	self == other
end

def evaluate(**options)

def evaluate(**options)
	self.class.new(nil, self.to_h, **options)
end

def evaluator

def evaluator
	Evaluator.new(self)
end

def export

Make a hash appropriate for a process environment
def export
	System.convert_to_shell(self)
end

def fetch(key, *default, &block)

def fetch(key, *default, &block)
	if environment = lookup(key)
		return environment.values[key]
	elsif block_given?
		yield(key, *default)
	elsif !default.empty?
		return default.first
	else
		raise KeyError.new("Environment missing #{key}")
	end
end

def flatten(**options)

def flatten(**options)
	hash = {}
	
	flatten_to_hash(hash)
	
	options[:name] ||= self.name
	
	return self.class.new(nil, hash, **options)
end

def flatten_to_array(array)

def flatten_to_array(array)
	if @parent
		@parent.flatten_to_array(array)
	end
	
	array << self
end

def flatten_to_hash(hash)

We fold in the ancestors one at a time from oldest to youngest.
def flatten_to_hash(hash)
	if parent = @parent
		parent = parent.flatten_to_hash(hash)
	end
	
	if @update
		self.dup(parent: parent).update!.update_hash(hash)
	else
		self.update_hash(hash)
	end
end

def freeze

def freeze
	return self if frozen?
	
	@parent.freeze
	@values.freeze
	@update.freeze
	
	super
end

def hash

def hash
	@parent.hash ^ @values.hash ^ @update.hash ^ @name.hash
end

def include?(key)

def include?(key)
	if @values.include?(key)
		true
	elsif @parent
		@parent.include?(key)
	end
end

def initialize(parent = nil, values = nil, name: nil, &block)

def initialize(parent = nil, values = nil, name: nil, &block)
	@parent = parent
	@values = (values || {}).to_h
	@update = block
	
	@name = name
end

def lookup(name)

def lookup(name)
	if @values.include? name
		self
	elsif @parent
		@parent.lookup(name)
	end
end

def merge(**options, &block)

def merge(**options, &block)
	self.class.new(self, **options, &block)
end

def size

def size
	@values.size + (@parent ? @parent.size : 0)
end

def sorted_keys

def sorted_keys
	@values.keys.sort_by(&:to_s)
end

def to_a

Convert the hierarchy of environments to an array where the parent comes before the child.
def to_a
	flat = []
	
	flatten_to_array(flat)
	
	return flat
end

def to_h

def to_h
	hash = {}
	
	# Flatten this chain of environments:
	flatten_to_hash(hash)
	
	# Evaluate all items to their respective object value:
	evaluator = Evaluator.new(hash)
	
	# Evaluate all the individual environment values so that they are flat:
	Hash[hash.map{|key, value| [key, evaluator.object_value(value)]}]
end

def to_s

def to_s
	buffer = String.new("\#<#{self.class} ")
	
	if @name
		buffer << @name.inspect << ' '
	end
	
	if @update
		buffer << @update.source_location.join(':') << ' '
	end
	
	buffer << @values.to_s << '>'
	
	return buffer
end

def update!

Apply the update function to this environment.
def update!
	construct!(self, &@update)
	@update = nil
	
	return self
end

def update_hash(hash)

def update_hash(hash)
	@values.each do |key, value|
		previous = hash[key]
		
		if Replace === value
			# Replace the parent value
			hash[key] = value
		elsif Default === value
			# Update the parent value if not defined.
			hash[key] = previous || value
		elsif Array === previous
			# Merge with the parent value
			hash[key] = previous + Array(value)
		elsif OpenStruct === value
			hash[key] = value.to_h
		else
			hash[key] = value
		end
	end
	
	return self
end