Skip to content

Commit

Permalink
Add a way to elide diffs
Browse files Browse the repository at this point in the history
When looking at a large diff for which many of the lines do not change,
it can be difficult to locate the lines which do. Text-oriented
diffs such as those you get from a conventional version control system
solve this problem by removing those unchanged lines from the diff
entirely. For instance, here is a section of the README with a line
removed. Notice that only the part of the file we care about, which is
around the line deleted, is displayed in the diff:

```
diff --git a/README.md b/README.md
index 56b046c..b38f4ca 100644
--- a/README.md
+++ b/README.md
@@ -169,7 +169,6 @@ SuperDiff.configure do |config|
   config.add_extra_differ_class(YourDiffer)
   config.add_extra_operation_tree_builder_class(YourOperationTreeBuilder)
   config.add_extra_operation_tree_class(YourOperationTree)
-  config.add_extra_diff_formatter_class(YourDiffFormatter)
 end
```

This commit implements a similar feature for data-oriented diffs. It
adds two new configuration options to allow you to control the elision
logic:

* `diff_elision_enabled` — The elision logic is disabled by default so
  as not to surprise people, so setting this to `true` will turn it on.
* `diff_elision_maximum` — This number controls what happens to
   unchanged lines (i.e. lines that are neither "insert" lines nor
   "delete" lines) that are in between changed lines. If a section of
   unchanged lines is beyond this number, the gem will elide (a fancy
   word for remove) the data structures within that section as much as
   possible until the limit is reached or it cannot go further. Elided
   lines are replaced with a `# ...` marker.

Here are a few examples:

\### Elision enabled

If you add this to your test helper:

``` ruby
SuperDiff.configure do |config|
  config.diff_elision_enabled = true
end
```

And you have this test:

``` ruby
expected = [
  "Afghanistan",
  "Aland Islands",
  "Albania",
  "American Samoa",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antarctica",
  "Antigua And Barbuda",
  "Argentina",
  "Aruba",
  "Australia"
]
actual = [
  "Afghanistan",
  "Aland Islands",
  "Algeria",
  "American Samoa",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antarctica",
  "Antigua And Barbuda",
  "Armenia",
  "Aruba",
  "Australia"
]
expect(actual).to eq(expected)
```

Then you will get a diff that looks like:

```
  [
    # ...
-   "Albania",
+   "Algeria",
    # ...
-   "Argentina",
+   "Armenia",
    "Aruba",
    "Australia"
  ]
```

\### Elision enabled and maximum specified

Configuration:

``` ruby
SuperDiff.configure do |config|
  config.diff_elision_enabled = true
  config.diff_elision_maximum = 5
end
```

Test:

```
expected = [
  "Afghanistan",
  "Aland Islands",
  "Albania",
  "American Samoa",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antarctica",
  "Antigua And Barbuda",
  "Argentina",
  "Aruba",
  "Australia"
]
actual = [
  "Afghanistan",
  "Aland Islands",
  "Algeria",
  "American Samoa",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antarctica",
  "Antigua And Barbuda",
  "Armenia",
  "Aruba",
  "Australia"
]
expect(actual).to eq(expected)
```

Resulting diff:

```
  [
    "Afghanistan",
    "Aland Islands",
-   "Albania",
+   "Algeria",
    "American Samoa",
    "Andorra",
    # ...
    "Antarctica",
    "Antigua And Barbuda",
-   "Argentina",
+   "Armenia",
    "Aruba",
    "Australia"
  ]
```

\### Elision enabled and maximum specified, but indentation limits complete elision

Configuration:

``` ruby
SuperDiff.configure do |config|
  config.diff_elision_enabled = true
end
```

Test:

``` ruby
expected = {
  foo: {
    bar: [
      "one",
      "two",
      "three"
    ],
    baz: "qux",
    fizz: "buzz",
    zing: "bing"
  }
}
actual = [
  foo: {
    bar: [
      "one",
      "two",
      "tree"
    ],
    baz: "qux",
    fizz: "buzz",
    zing: "bing"
  }
]
expect(actual).to eq(expected)
```

Resulting diff:

```
  {
    foo: {
      bar: [
        # ...
-       "three"
+       "tree"
      ],
      # ...
    }
  }
```

Notice how we cannot reach a maximum of 0 in this case because otherwise
the diff would look like this and it wouldn't make sense:

```
  # ...
-       "three"
+       "tree"
      ],
      # ...
    }
  }
```
  • Loading branch information
mcmire committed May 9, 2021
1 parent b911118 commit c94cbbf
Show file tree
Hide file tree
Showing 173 changed files with 15,674 additions and 2,483 deletions.
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Changelog

## Unreleased

### Breaking changes

* Continuing with the previous release, diff formatters are now gone in favor of
operation tree flatteners. If you have a custom diff formatter, you will want
to inherit from SuperDiff::DiffFormatters::Base (or an appropriate subclass).
Additionally, the `add_extra_diff_formatter_class` configuration option has
disappeared; instead, operation tree classes are expected to have an
`operation_tree_flattener_class` method.

### Features

* Add the ability to compress long diffs by eliding unimportant sections. This
functionality is not enabled by default; rather, you will need to activate it
and then configure it. At a minimum, you will want to add something like this
to your spec helper:

``` ruby
SuperDiff.configure do |config|
config.diff_elision_enabled = true
config.diff_elision_maximum = 10
end
```

Here, `diff_elision_maximum` controls the minimum number of lines that are
changed that you want to see in between lines that are unchanged (everything
else will be elided as much as possible in a way that makes sense). Here is
another possible configuration:

``` ruby
SuperDiff.configure do |config|
config.diff_elision_enabled = true
config.diff_elision_maximum = 10
config.diff_elision_padding = 5
end
```

Here, `diff_elision_padding` controls the amount of context that you want to
preserve around changed lines in the diff. This makes this functionality act
closer to diffs you might see in a version control system.

### Features

* Update inspection of Doubles to include stubbed methods and their values.

### Improvements

* Change how objects are inspected on a single line so that instance variables
are always sorted.
* Make a tweak to how hashes are presented in diffs and inspections: a hash that
has a mixture of symbols and strings will be presented as though all keys are
strings (i.e. hashrocket syntax).

## 0.7.0 - 2021-05-07

### Features
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ SuperDiff.configure do |config|
config.add_extra_differ_class(YourDiffer)
config.add_extra_operation_tree_builder_class(YourOperationTreeBuilder)
config.add_extra_operation_tree_class(YourOperationTree)
config.add_extra_diff_formatter_class(YourDiffFormatter)
end
```

Expand Down
7 changes: 6 additions & 1 deletion config/zeus_plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ class CustomZeusPlan < Zeus::Plan
:run_rspec_rails_test,
)

def initialize(using_outside_of_zeus: false, color_enabled: false)
def initialize(
using_outside_of_zeus: false,
color_enabled: false,
configuration: {}
)
@test_plan = TestPlan.new(
using_outside_of_zeus: using_outside_of_zeus,
color_enabled: color_enabled,
configuration: configuration,
)
end
end
Expand Down
33 changes: 27 additions & 6 deletions lib/super_diff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module SuperDiff
:ColorizedDocumentExtensions,
"super_diff/colorized_document_extensions",
)
autoload :OperationTreeFlatteners, "super_diff/operation_tree_flatteners"
autoload :Configuration, "super_diff/configuration"
autoload :Csi, "super_diff/csi"
autoload :DiffFormatters, "super_diff/diff_formatters"
Expand All @@ -16,6 +17,10 @@ module SuperDiff
autoload :GemVersion, "super_diff/gem_version"
autoload :Helpers, "super_diff/helpers"
autoload :ImplementationChecks, "super_diff/implementation_checks"
autoload :Line, "super_diff/line"
autoload :TieredLines, "super_diff/tiered_lines"
autoload :TieredLinesElider, "super_diff/tiered_lines_elider"
autoload :TieredLinesFormatter, "super_diff/tiered_lines_formatter"
autoload :ObjectInspection, "super_diff/object_inspection"
autoload :OperationTrees, "super_diff/operation_trees"
autoload :OperationTreeBuilders, "super_diff/operation_tree_builders"
Expand All @@ -24,18 +29,25 @@ module SuperDiff

def self.configure
yield configuration
configuration.updated
end

def self.configuration
@_configuration ||= Configuration.new
end

def self.inspect_object(object, as_single_line:, indent_level: 0)
ObjectInspection::Inspectors::Main.call(
object,
as_single_line: as_single_line,
indent_level: indent_level,
)
def self.inspect_object(object, as_lines:, **rest)
SuperDiff::RecursionGuard.guarding_recursion_of(object) do
inspection_tree = ObjectInspection::InspectionTreeBuilders::Main.call(
object
)

if as_lines
inspection_tree.render_to_lines(object, **rest)
else
inspection_tree.render_to_string(object)
end
end
end

def self.time_like?(value)
Expand All @@ -45,6 +57,15 @@ def self.time_like?(value)
value.is_a?(Time)
end

def self.primitive?(value)
case value
when true, false, nil, Symbol, Numeric, Regexp, Class
true
else
false
end
end

def self.insert_overrides(target_module, mod = nil, &block)
if mod
target_module.prepend(mod)
Expand Down
14 changes: 7 additions & 7 deletions lib/super_diff/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

module SuperDiff
module ActiveRecord
autoload :DiffFormatters, "super_diff/active_record/diff_formatters"
autoload :Differs, "super_diff/active_record/differs"
autoload(
:ObjectInspection,
Expand All @@ -16,6 +15,10 @@ module ActiveRecord
:OperationTreeBuilders,
"super_diff/active_record/operation_tree_builders",
)
autoload(
:OperationTreeFlatteners,
"super_diff/active_record/operation_tree_flatteners",
)

SuperDiff.configure do |config|
config.add_extra_differ_classes(
Expand All @@ -25,12 +28,9 @@ module ActiveRecord
OperationTreeBuilders::ActiveRecordModel,
OperationTreeBuilders::ActiveRecordRelation,
)
config.add_extra_diff_formatter_classes(
DiffFormatters::ActiveRecordRelation,
)
config.add_extra_inspector_classes(
ObjectInspection::Inspectors::ActiveRecordModel,
ObjectInspection::Inspectors::ActiveRecordRelation,
config.add_extra_inspection_tree_builder_classes(
ObjectInspection::InspectionTreeBuilders::ActiveRecordModel,
ObjectInspection::InspectionTreeBuilders::ActiveRecordRelation,
)
end
end
Expand Down
10 changes: 0 additions & 10 deletions lib/super_diff/active_record/diff_formatters.rb

This file was deleted.

This file was deleted.

16 changes: 3 additions & 13 deletions lib/super_diff/active_record/differs/active_record_relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,10 @@ def self.applies_to?(expected, actual)
actual.is_a?(::ActiveRecord::Relation)
end

def call
DiffFormatters::ActiveRecordRelation.call(
operation_tree,
indent_level: indent_level,
)
end

private
protected

def operation_tree
OperationTreeBuilders::ActiveRecordRelation.call(
expected: expected,
actual: actual,
)
def operation_tree_builder_class
OperationTreeBuilders::ActiveRecordRelation
end
end
end
Expand Down
8 changes: 2 additions & 6 deletions lib/super_diff/active_record/object_inspection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ module SuperDiff
module ActiveRecord
module ObjectInspection
autoload(
:Inspectors,
"super_diff/active_record/object_inspection/inspectors",
)
autoload(
:MapExtension,
"super_diff/active_record/object_inspection/map_extension",
:InspectionTreeBuilders,
"super_diff/active_record/object_inspection/inspection_tree_builders",
)
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module SuperDiff
module ActiveRecord
module ObjectInspection
module InspectionTreeBuilders
autoload(
:ActiveRecordModel,
"super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model",
)
autoload(
:ActiveRecordRelation,
"super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation",
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,44 +1,43 @@
module SuperDiff
module ActiveRecord
module ObjectInspection
module Inspectors
class ActiveRecordModel < SuperDiff::ObjectInspection::Inspectors::Base
module InspectionTreeBuilders
class ActiveRecordModel < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base
def self.applies_to?(value)
value.is_a?(::ActiveRecord::Base)
end

protected

def inspection_tree
def call
SuperDiff::ObjectInspection::InspectionTree.new do
add_text do |object|
"#<#{object.class} "
end
as_lines_when_rendering_to_lines(collection_bookend: :open) do
add_text do |object|
"#<#{object.class} "
end

when_multiline do
add_text "{"
when_rendering_to_lines do
add_text "{"
end
end

nested do |object|
add_break

insert_separated_list(
["id"] + (object.attributes.keys.sort - ["id"]),
separator: ",",
) do |name|
add_text name
add_text ": "
as_prefix_when_rendering_to_lines do
add_text "#{name}: "
end

add_inspection_of object.read_attribute(name)
end
end

add_break
as_lines_when_rendering_to_lines(collection_bookend: :close) do
when_rendering_to_lines do
add_text "}"
end

when_multiline do
add_text "}"
add_text ">"
end

add_text ">"
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
module SuperDiff
module ActiveRecord
module ObjectInspection
module Inspectors
class ActiveRecordRelation < SuperDiff::ObjectInspection::Inspectors::Base
module InspectionTreeBuilders
class ActiveRecordRelation < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base
def self.applies_to?(value)
value.is_a?(::ActiveRecord::Relation)
end

protected

def inspection_tree
def call
SuperDiff::ObjectInspection::InspectionTree.new do
add_text "#<ActiveRecord::Relation ["
as_lines_when_rendering_to_lines(collection_bookend: :open) do
add_text "#<ActiveRecord::Relation ["
end

nested do |array|
insert_array_inspection_of(array)
end

add_break
add_text "]>"
as_lines_when_rendering_to_lines(collection_bookend: :close) do
add_text "]>"
end
end
end
end
Expand Down
16 changes: 0 additions & 16 deletions lib/super_diff/active_record/object_inspection/inspectors.rb

This file was deleted.

Loading

0 comments on commit c94cbbf

Please sign in to comment.