Module: Philiprehberger::Differ

Defined in:
lib/philiprehberger/differ.rb,
lib/philiprehberger/differ/change.rb,
lib/philiprehberger/differ/version.rb,
lib/philiprehberger/differ/changeset.rb,
lib/philiprehberger/differ/comparator.rb,
lib/philiprehberger/differ/formatters.rb,
lib/philiprehberger/differ/similarity.rb

Defined Under Namespace

Modules: Formatters, Similarity Classes: Change, Changeset, Comparator, Error

Constant Summary collapse

VERSION =
'0.5.0'

Class Method Summary collapse

Class Method Details

.breaking_changes?(changeset) ⇒ Boolean

Detect if a changeset contains breaking changes (removals or type changes)

Parameters:

  • changeset (Changeset)

    the changeset to check

Returns:

  • (Boolean)

    true if breaking changes are detected



88
89
90
91
92
93
# File 'lib/philiprehberger/differ.rb', line 88

def self.breaking_changes?(changeset)
  changeset.changes.any? do |change|
    change.type == :removed ||
      (change.type == :changed && change.old_value.class != change.new_value.class)
  end
end

.diff(old_val, new_val, ignore: [], array_key: nil) ⇒ Object



14
15
16
17
# File 'lib/philiprehberger/differ.rb', line 14

def self.diff(old_val, new_val, ignore: [], array_key: nil)
  changes = Comparator.call(old_val, new_val, ignore: ignore, array_key: array_key)
  Changeset.new(changes)
end

.merge(base, theirs, ours) ⇒ Hash

Perform a three-way merge with conflict detection

Parameters:

  • base (Hash)

    the common ancestor

  • theirs (Hash)

    their changes

  • ours (Hash)

    our changes

Returns:

  • (Hash)

    { merged: Hash, conflicts: Array }



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/philiprehberger/differ.rb', line 42

def self.merge(base, theirs, ours)
  their_changes = Comparator.call(base, theirs)
  our_changes = Comparator.call(base, ours)

  conflicts = detect_conflicts(their_changes, our_changes)
  conflict_paths = conflicts.map { |c| c[:path] }

  merged = deep_dup(base)
  (their_changes + our_changes).each do |change|
    next if conflict_paths.include?(change.path)

    apply_merge_change(merged, change)
  end

  { merged: merged, conflicts: conflicts }
end

.similarity(old_val, new_val, ignore: [], array_key: nil) ⇒ Object



19
20
21
# File 'lib/philiprehberger/differ.rb', line 19

def self.similarity(old_val, new_val, ignore: [], array_key: nil)
  Similarity.call(old_val, new_val, ignore: ignore, array_key: array_key)
end

.stats(changeset) ⇒ Hash{Symbol => Integer}

Structured count summary of a changeset.

Returns a hash of integer counts for the added, removed, and changed entries in the changeset, along with a running total and the number of unique paths. This method is read-only and never mutates the changeset.

Parameters:

  • changeset (Changeset)

    the changeset to summarize

Returns:

  • (Hash{Symbol => Integer})

    counts by kind — ‘{ added:, removed:, changed:, total:, paths: }`

Raises:

  • (ArgumentError)

    if ‘changeset` is not a Changeset



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/philiprehberger/differ.rb', line 68

def self.stats(changeset)
  raise ArgumentError, 'changeset must be a Philiprehberger::Differ::Changeset' unless changeset.is_a?(Changeset)

  added   = changeset.added.length
  removed = changeset.removed.length
  changed = changeset.changed.length

  {
    added: added,
    removed: removed,
    changed: changed,
    total: added + removed + changed,
    paths: changeset.paths.length
  }
end

.subset(changeset, path) ⇒ Changeset

Filter changeset to only changes under a specific path prefix

Parameters:

  • changeset (Changeset)

    the changeset to filter

  • path (String)

    the path prefix to filter by

Returns:

  • (Changeset)

    new changeset with only matching changes



28
29
30
31
32
33
34
# File 'lib/philiprehberger/differ.rb', line 28

def self.subset(changeset, path)
  prefix = path.to_s
  filtered = changeset.changes.select do |change|
    change.path.to_s == prefix || change.path.to_s.start_with?("#{prefix}.")
  end
  Changeset.new(filtered)
end