module Quby module Compiler module Services class SeedDiff def format_patch(reference, candidate, path:) @reference_for_debugging = reference @candidate_for_debugging = candidate format_polymorph_patch(reference, candidate, path: path) end def apply_patch(object, patch) return object if patch.nil? apply_polymorphic(object, patch, path: "") end private def format_polymorph_patch(reference, candidate, path:) if reference.class != candidate.class && !candidate.nil? && !depends_relation?(path) raise "Incompatible types at #{path}: #{reference.class} and #{candidate.class}" end case reference when Hash case candidate when Hash format_hash_patch(reference, candidate, path: path) else reference end when Array format_array_patch(reference, candidate, path: path) when String, Symbol, true, false reference else raise "Cannot patch #{reference.class} yet." end end def format_hash_patch(reference, candidate, path:) return reference if candidate.nil? patch = {} reference.keys.each do |key| if reference[key] != candidate[key] patch[key] = format_polymorph_patch(reference[key], candidate[key], path: path + ".#{key}") end end patch end def format_array_patch(reference, candidate, path:) return reference if candidate.nil? patch = { __patch_type__: "array" } reference.each_with_index do |reference_elm, idx| if reference_elm != candidate[idx] patch[idx] = reference_elm end end if candidate.size > reference.size patch[:__patch_trim__] = reference.size end patch end def apply_polymorphic(object, patch, path:) if patch.is_a?(Hash) && patch[:__patch_type__] == "array" apply_array(object, patch, path: path) elsif patch.is_a?(Hash) apply_hash(object, patch, path: path) else patch end rescue Exception puts "path: #{path}" raise end def apply_hash(object, patch, path:) object = {} unless object.is_a?(Hash) patch.each do |key, value| object[key] = apply_polymorphic(object[key], value, path: path + ".#{key}") end object end def apply_array(object, patch, path:) object ||= [] patch.each do |key, val| next if key == :__patch_type__ || key == :__patch_trim__ object[key] = val end if patch[:__patch_trim__] object = object.slice(0, patch[:__patch_trim__]) end object end def depends_relation?(path) path.end_with?(".depends") end end end end end