dockerfiles/scripts/helpers/merge-yaml.rb (69 lines of code) (raw):
#!/usr/bin/env ruby
require 'yaml'
require 'erb'
def deep_merge!(hash, other_hash, &block)
other_hash.each_pair do |current_key, other_value|
this_value = hash[current_key]
hash[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
deep_merge(this_value, other_value, &block)
else
if block_given? && key?(current_key)
block.call(current_key, this_value, other_value)
else
other_value
end
end
end
hash
end
def deep_merge(hash, other_hash, &block)
deep_merge!(hash.dup, other_hash, &block)
end
# Taken from: https://stackoverflow.com/a/55705853
def duplicate_keys(content)
duplicate_keys = []
validator = ->(node, parent_path) do
if node.is_a?(Psych::Nodes::Mapping)
children = node.children.each_slice(2) # In a Mapping, every other child is the key node, the other is the value node.
duplicates = children.map(&:first).group_by(&:value).select { |_value, nodes| nodes.size > 1 }
duplicates.each do |key, nodes|
duplicate_keys.append(
key: parent_path + [key],
occurrences: nodes.map { |occurrence| "line: #{occurrence.start_line + 1}" }
)
end
children.each do |key_node, value_node|
key_node_value = key_node.respond_to?(:value) ? key_node.value : nil
validator.call(value_node, parent_path + [key_node_value].compact)
end
else
node.children.to_a.each { |child| validator.call(child, parent_path) }
end
end
ast = Psych.parse_stream(content)
validator.call(ast, [])
duplicate_keys if duplicate_keys.any?
end
config = ARGV.inject({}) do |config, arg|
file_name, *digs = arg.split(':')
# we dig into our config
content = File.read(file_name)
content = ERB.new(content).result
unless file_name.end_with?('.example')
if duplicates = duplicate_keys(content)
STDERR.puts "Found #{duplicates.count} duplicates in #{file_name}:"
duplicates.each do |duplicate|
STDERR.puts "- '#{duplicate[:key].join('.')} at #{duplicate[:occurrences].join(', ')}"
end
exit 1
end
end
begin
content = Psych.unsafe_load(content, filename: file_name) || {}
content = content.dig(*digs) if digs.any?
deep_merge(config, content || {})
rescue => e
STDERR.puts "EXCEPTION:"
STDERR.puts "#{file_name}: #{e.message}"
STDERR.puts e.backtrace
exit 1
end
end
puts config.to_yaml