diffux-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80f32154fc059c048c223e8e2ca3646a802a8859
4
+ data.tar.gz: 7452b31f9989d95cbc3799d5bcacbe1e4dcd99b7
5
+ SHA512:
6
+ metadata.gz: 240ad46905dfd690d5c3fcafc82eff833ba0c0128a3e786c08089dc40cf33b720efda4f9d971e70f2767eb975829d7a5971981983c5b5be2acaf352ae923a383
7
+ data.tar.gz: 60e6f38b354ad99791fad9c333ed52e9e038f7eee7ac823c3eebcd92ee84294dfae794436f64e0717babf168e896380dfd08ee66b92564ce23957fef2fd42a3c
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'diffux_core'
4
+ require 'oily_png'
5
+ require 'optparse'
6
+
7
+ options = {
8
+ outfile: 'diff.png'
9
+ }
10
+ OptionParser.new do |opts|
11
+ opts.banner = 'Usage: diffux-compare [options]'
12
+ opts.separator ''
13
+ opts.separator 'Options:'
14
+
15
+ opts.on('-b', '--before-image BEFORE_IMAGE',
16
+ 'Specify a path to the before-image') do |img|
17
+ options[:before_image] = img
18
+ end
19
+ opts.on('-a', '--after-image AFTER_IMAGE',
20
+ 'Specify a path to the after-image') do |img|
21
+ options[:after_image] = img
22
+ end
23
+
24
+ opts.on('-o', '--outfile OUTFILE',
25
+ 'Specify where the diff image will be saved (if needed)') do |f|
26
+ options[:outfile] = f
27
+ end
28
+
29
+ opts.on_tail('-h', '--help', 'Show this message') do
30
+ puts opts
31
+ exit
32
+ end
33
+ end.parse!(ARGV)
34
+
35
+ help = '(use `diffux-compare -h` to see options)'
36
+ unless options[:before_image]
37
+ puts "Missing BEFORE_IMAGE #{help}"
38
+ exit 1
39
+ end
40
+ unless options[:after_image]
41
+ puts "Missing AFTER_IMAGE #{help}"
42
+ exit 1
43
+ end
44
+
45
+ comparison = Diffux::SnapshotComparer.new(
46
+ ChunkyPNG::Image.from_file(options[:before_image]),
47
+ ChunkyPNG::Image.from_file(options[:after_image]),
48
+ ).compare!
49
+
50
+ if img = comparison[:diff_image]
51
+ img.save(options[:outfile])
52
+ puts "DIFF: #{comparison[:diff_in_percent]}%"
53
+ exit 10
54
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'diffux_core'
4
+ require 'optparse'
5
+
6
+ options = {
7
+ width: 320
8
+ }
9
+ OptionParser.new do |opts|
10
+ opts.banner = 'Usage: diffux-snapshot [options]'
11
+ opts.separator ''
12
+ opts.separator 'Options:'
13
+
14
+ opts.on('-u', '--url URL',
15
+ 'Set a URL to snapshot') do |url|
16
+ options[:url] = url
17
+ end
18
+
19
+ opts.on('-o', '--outfile OUTFILE',
20
+ 'Specify where the snapshot will be saved') do |f|
21
+ options[:outfile] = f
22
+ end
23
+
24
+ opts.on('-w', '--width WIDTH', Integer,
25
+ 'Set the width of the screen (defaults to 320)') do |w|
26
+ options[:width] = w
27
+ end
28
+
29
+ opts.on('-a', '--useragent USERAGENT',
30
+ 'Set a useragent header when loading the url') do |ua|
31
+ options[:useragent] = ua
32
+ end
33
+
34
+ opts.on_tail('-h', '--help', 'Show this message') do
35
+ puts opts
36
+ exit
37
+ end
38
+ end.parse!(ARGV)
39
+
40
+ help = '(use `diffux-snapshot -h` to see options)'
41
+ unless options[:url]
42
+ puts "Missing url #{help}"
43
+ exit 1
44
+ end
45
+ unless options[:outfile]
46
+ puts "Missing outfile #{help}"
47
+ exit 1
48
+ end
49
+
50
+ snapshot = Diffux::Snapshotter.new(
51
+ viewport_width: options[:width],
52
+ user_agent: options[:useragent],
53
+ outfile: options[:outfile],
54
+ url: options[:url],
55
+ ).take_snapshot!
56
+
@@ -0,0 +1,3 @@
1
+ require 'diffux_core/version'
2
+ require 'diffux_core/snapshotter'
3
+ require 'diffux_core/snapshot_comparer'
@@ -0,0 +1,47 @@
1
+ require 'set'
2
+ module Diffux
3
+ # This class finds clusters in a diff. A cluster is defined as rows that are
4
+ # different, and are closer than DIFF_ROW_THRESHOLD pixels to its neighboring
5
+ # diff row.
6
+ class DiffClusterFinder
7
+ MAXIMUM_ADJACENCY_GAP = 5
8
+
9
+ # @param number_of_rows [Numeric]
10
+ def initialize(number_of_rows)
11
+ @number_of_rows = number_of_rows
12
+ @rows_with_diff = SortedSet.new
13
+ end
14
+
15
+ # Tell the DiffClusterFinder about a row that is different.
16
+ #
17
+ # @param row [Numeric]
18
+ def row_is_different(row)
19
+ @rows_with_diff.add row
20
+ end
21
+
22
+ # @return [Float] the percent of rows that are different
23
+ def percent_of_rows_different
24
+ @rows_with_diff.length.to_f / @number_of_rows * 100
25
+ end
26
+
27
+ # Calculate clusters from diff-rows that are close to each other.
28
+ #
29
+ # @return [Array<Hash>] a list of clusters modeled as hashes:
30
+ # `{ start: x, finish: y }`
31
+ def clusters
32
+ results = []
33
+ @rows_with_diff.each do |row|
34
+ current = results.last
35
+ if !current || current[:finish] + MAXIMUM_ADJACENCY_GAP < row
36
+ results << {
37
+ start: row,
38
+ finish: row,
39
+ }
40
+ else
41
+ current[:finish] = row
42
+ end
43
+ end
44
+ results
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,72 @@
1
+ require 'oily_png'
2
+ require 'diff-lcs'
3
+ require_relative 'diff_cluster_finder'
4
+
5
+ module Diffux
6
+ # This class is responsible for comparing two Snapshots and generating a diff.
7
+ class SnapshotComparer
8
+ # @param png_before [ChunkyPNG::Image]
9
+ # @param png_after [ChunkyPNG::Image]
10
+ def initialize(png_before, png_after)
11
+ @png_after = png_after
12
+ @png_before = png_before
13
+ end
14
+
15
+ # @return [Hash]
16
+ def compare!
17
+ sdiff = Diff::LCS.sdiff(to_array_of_arrays(@png_before),
18
+ to_array_of_arrays(@png_after))
19
+ cluster_finder = DiffClusterFinder.new(sdiff.size)
20
+ sprite, all_comparisons = initialize_comparison_images(
21
+ [@png_after.width, @png_before.width].max, sdiff.size)
22
+
23
+ sdiff.each_with_index do |row, y|
24
+ # each row is a Diff::LCS::ContextChange instance
25
+ all_comparisons.each { |image| image.render_row(y, row) }
26
+ cluster_finder.row_is_different(y) unless row.unchanged?
27
+ end
28
+
29
+ percent_changed = cluster_finder.percent_of_rows_different
30
+ {
31
+ diff_in_percent: percent_changed,
32
+ diff_image: (sprite if percent_changed > 0),
33
+ diff_clusters: cluster_finder.clusters,
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ # @param [ChunkyPNG::Image]
40
+ # @return [Array<Array<Integer>>]
41
+ def to_array_of_arrays(chunky_png)
42
+ array_of_arrays = []
43
+ chunky_png.height.times do |y|
44
+ array_of_arrays << chunky_png.row(y)
45
+ end
46
+ array_of_arrays
47
+ end
48
+
49
+ # @param canvas [ChunkyPNG::Image] The output image to draw pixels on
50
+ # @return [Array<SnapshotComparisonImage>]
51
+ def initialize_comparison_images(width, height)
52
+ gutter_width = SnapshotComparisonImage::Gutter::WIDTH
53
+ total_width = (width * 3) + (gutter_width * 3)
54
+
55
+ sprite = ChunkyPNG::Image.new(total_width, height)
56
+ offset, comparison_images = 0, []
57
+ comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
58
+ offset += gutter_width
59
+ comparison_images << SnapshotComparisonImage::Before.new(offset, sprite)
60
+ offset += width
61
+ comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
62
+ offset += gutter_width
63
+ comparison_images << SnapshotComparisonImage::Overlayed.new(offset, sprite)
64
+ offset += width
65
+ comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
66
+ offset += gutter_width
67
+ comparison_images << SnapshotComparisonImage::After.new(offset, sprite)
68
+
69
+ [sprite, comparison_images]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ module Diffux
2
+ module SnapshotComparisonImage
3
+ # This subclass of `SnapshotComparisonImage` knows how to draw the
4
+ # representation of the "after" image.
5
+ class After < SnapshotComparisonImage::Base
6
+ # @param y [Integer]
7
+ # @param row [Diff::LCS:ContextChange]
8
+ def render_changed_row(y, row)
9
+ render_added_row(y, row)
10
+ end
11
+
12
+ # @param y [Integer]
13
+ # @param row [Diff::LCS:ContextChange]
14
+ def render_added_row(y, row)
15
+ row.new_element.each_with_index do |pixel_after, x|
16
+ render_pixel(x, y, pixel_after)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,115 @@
1
+ require 'chunky_png'
2
+ module Diffux
3
+ module SnapshotComparisonImage
4
+ # This model represents a "comparison image". Basically it's just a wrapper
5
+ # around a ChunkyPNG image with some nice methods to make life easier in the
6
+ # world of diffs.
7
+ #
8
+ # This model is never persisted.
9
+ class Base
10
+ include ChunkyPNG::Color
11
+
12
+ BASE_OPACITY = 0.1
13
+ BASE_ALPHA = (255 * BASE_OPACITY).round
14
+ BASE_DIFF_ALPHA = BASE_ALPHA * 2
15
+
16
+ MAGENTA = ChunkyPNG::Color.from_hex '#b33682'
17
+ RED = ChunkyPNG::Color.from_hex '#dc322f'
18
+ GREEN = ChunkyPNG::Color.from_hex '#859900'
19
+
20
+ # @param offset [Integer] the x-offset that this comparison image should
21
+ # use when rendering on the canvas image.
22
+ # @param canvas [ChunkyPNG::Image] The canvas image to render pixels on.
23
+ def initialize(offset, canvas)
24
+ @offset = offset
25
+ @canvas = canvas
26
+ end
27
+
28
+ # @param y [Integer]
29
+ # @param row [Diff::LCS:ContextChange]
30
+ def render_row(y, row)
31
+ if row.unchanged?
32
+ render_unchanged_row(y, row)
33
+ elsif row.deleting?
34
+ render_deleted_row(y, row)
35
+ elsif row.adding?
36
+ render_added_row(y, row)
37
+ else # changing?
38
+ render_changed_row(y, row)
39
+ end
40
+ end
41
+
42
+ # @param y [Integer]
43
+ # @param row [Diff::LCS:ContextChange]
44
+ def render_unchanged_row(y, row)
45
+ row.new_element.each_with_index do |pixel, x|
46
+ # Render the unchanged pixel as-is
47
+ render_pixel(x, y, pixel)
48
+ end
49
+ end
50
+
51
+ # @param y [Integer]
52
+ # @param row [Diff::LCS:ContextChange]
53
+ def render_changed_row(y, row)
54
+ # no default implementation
55
+ end
56
+
57
+ # @param y [Integer]
58
+ # @param row [Diff::LCS:ContextChange]
59
+ def render_added_row(y, row)
60
+ # no default implementation
61
+ end
62
+
63
+ # @param y [Integer]
64
+ # @param row [Diff::LCS:ContextChange]
65
+ def render_deleted_row(y, row)
66
+ # no default implementation
67
+ end
68
+
69
+ # Could be simplified as ChunkyPNG::Color::MAX * 2, but this format mirrors
70
+ # the math in #pixel_diff_score
71
+ MAX_EUCLIDEAN_DISTANCE = Math.sqrt(ChunkyPNG::Color::MAX**2 * 4)
72
+
73
+ # Compute a score that represents the difference between 2 pixels
74
+ #
75
+ # This method simply takes the Euclidean distance between the RGBA channels
76
+ # of 2 colors over the maximum possible Euclidean distance. This gives us a
77
+ # percentage of how different the two colors are.
78
+ #
79
+ # Although it would be more perceptually accurate to calculate a proper
80
+ # Delta E in Lab colorspace, we probably don't need perceptual accuracy for
81
+ # this application, and it is nice to avoid the overhead of converting RGBA
82
+ # to Lab.
83
+ #
84
+ # @param pixel_after [Integer]
85
+ # @param pixel_before [Integer]
86
+ # @return [Float] number between 0 and 1 where 1 is completely different
87
+ # and 0 is no difference
88
+ def pixel_diff_score(pixel_after, pixel_before)
89
+ Math.sqrt(
90
+ (r(pixel_after) - r(pixel_before))**2 +
91
+ (g(pixel_after) - g(pixel_before))**2 +
92
+ (b(pixel_after) - b(pixel_before))**2 +
93
+ (a(pixel_after) - a(pixel_before))**2
94
+ ) / MAX_EUCLIDEAN_DISTANCE
95
+ end
96
+
97
+ # @param diff_score [Float]
98
+ # @return [Integer] a number between 0 and 255 that represents the alpha
99
+ # channel of of the difference
100
+ def diff_alpha(diff_score)
101
+ (BASE_DIFF_ALPHA + ((255 - BASE_DIFF_ALPHA) * diff_score)).round
102
+ end
103
+
104
+ # Renders a pixel on the specified x and y position. Uses the offset that
105
+ # the comparison image has been configured with.
106
+ #
107
+ # @param x [Integer]
108
+ # @param y [Integer]
109
+ # @param pixel [Integer]
110
+ def render_pixel(x, y, pixel)
111
+ @canvas.set_pixel(x + @offset, y, pixel)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,21 @@
1
+ module Diffux
2
+ module SnapshotComparisonImage
3
+ # This subclass of `SnapshotComparisonImage` knows how to draw the
4
+ # representation of the "before" image.
5
+ class Before < SnapshotComparisonImage::Base
6
+ # @param y [Integer]
7
+ # @param row [Diff::LCS:ContextChange]
8
+ def render_changed_row(y, row)
9
+ render_deleted_row(y, row)
10
+ end
11
+
12
+ # @param y [Integer]
13
+ # @param row [Diff::LCS:ContextChange]
14
+ def render_deleted_row(y, row)
15
+ row.old_element.each_with_index do |pixel_before, x|
16
+ render_pixel(x, y, pixel_before)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Diffux
2
+ module SnapshotComparisonImage
3
+ # This class renders a gutter-column with a color representing the type of
4
+ # change that has happened.
5
+ class Gutter < SnapshotComparisonImage::Base
6
+ WIDTH = 10
7
+ GRAY = ChunkyPNG::Color.from_hex '#cccccc'
8
+
9
+ def render_row(y, row)
10
+ WIDTH.times do |x|
11
+ render_pixel(x, y, gutter_color(row))
12
+ end
13
+ # render a two-pixel empty column
14
+ 2.times do |x|
15
+ render_pixel(WIDTH - 1 - x, y, WHITE)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def gutter_color(row)
22
+ if row.unchanged?
23
+ WHITE
24
+ elsif row.deleting?
25
+ RED
26
+ elsif row.adding?
27
+ GREEN
28
+ else # changed?
29
+ GRAY
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,88 @@
1
+ module Diffux
2
+ module SnapshotComparisonImage
3
+ # This subclass of `SnapshotComparisonImage` knows how to overlay the
4
+ # after-image on top of the before-image, and render the difference in a
5
+ # scaled magenta color.
6
+ class Overlayed < SnapshotComparisonImage::Base
7
+ WHITE_OVERLAY = ChunkyPNG::Color.fade(WHITE, 1 - BASE_ALPHA)
8
+
9
+ # @param offset [Integer]
10
+ # @param canvas [ChunkyPNG::Image]
11
+ # @see SnapshotComparisonImage::Base
12
+ def initialize(offset, canvas)
13
+ @diff_pixels = {}
14
+ @faded_pixels = {}
15
+ super
16
+ end
17
+
18
+ # @param y [Integer]
19
+ # @param row [Diff::LCS:ContextChange]
20
+ def render_unchanged_row(y, row)
21
+ # Render translucent original pixels
22
+ row.new_element.each_with_index do |pixel, x|
23
+ render_faded_pixel(x, y, pixel)
24
+ end
25
+ end
26
+
27
+ # @param y [Integer]
28
+ # @param row [Diff::LCS:ContextChange]
29
+ def render_deleted_row(y, row)
30
+ row.old_element.each_with_index do |pixel_before, x|
31
+ render_faded_magenta_pixel(TRANSPARENT, pixel_before, x, y)
32
+ end
33
+ end
34
+
35
+ # @param y [Integer]
36
+ # @param row [Diff::LCS:ContextChange]
37
+ def render_added_row(y, row)
38
+ row.new_element.each_with_index do |pixel_after, x|
39
+ render_faded_magenta_pixel(pixel_after, TRANSPARENT, x, y)
40
+ end
41
+ end
42
+
43
+ # @param y [Integer]
44
+ # @param row [Diff::LCS:ContextChange]
45
+ def render_changed_row(y, row)
46
+ row.old_element.zip(row.new_element).each_with_index do |pixels, x|
47
+ pixel_before, pixel_after = pixels
48
+ render_faded_magenta_pixel(
49
+ pixel_after || TRANSPARENT,
50
+ pixel_before || TRANSPARENT,
51
+ x, y)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # @param pixel_after [Integer]
58
+ # @param pixel_before [Integer]
59
+ # @param x [Integer]
60
+ # @param y [Integer]
61
+ def render_faded_magenta_pixel(pixel_after, pixel_before, x, y)
62
+ score = pixel_diff_score(pixel_after, pixel_before)
63
+ if score > 0
64
+ render_diff_pixel(x, y, score)
65
+ else
66
+ render_faded_pixel(x, y, pixel_after)
67
+ end
68
+ end
69
+
70
+ # @param x [Integer]
71
+ # @param y [Integer]
72
+ # @param score [Float]
73
+ def render_diff_pixel(x, y, score)
74
+ @diff_pixels[score] ||= compose_quick(fade(MAGENTA, diff_alpha(score)),
75
+ WHITE)
76
+ render_pixel(x, y, @diff_pixels[score])
77
+ end
78
+
79
+ # @param x [Integer]
80
+ # @param y [Integer]
81
+ # @param pixel [Integer]
82
+ def render_faded_pixel(x, y, pixel)
83
+ @faded_pixels[pixel] ||= compose_quick(WHITE_OVERLAY, pixel)
84
+ render_pixel(x, y, @faded_pixels[pixel])
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,66 @@
1
+ require 'phantomjs'
2
+ require 'json'
3
+ %w(base gutter before after overlayed).each do |type|
4
+ require_relative "snapshot_comparison_image/#{type}"
5
+ end
6
+ module Diffux
7
+ # Snapshotter is responsible for delegating to PhantomJS to take the snapshot
8
+ # for a given URL and viewoprt, and then saving that snapshot to a file and
9
+ # storing any metadata on the Snapshot object.
10
+ class Snapshotter
11
+ SCRIPT_PATH = File.join(File.dirname(__FILE__), 'script/take-snapshot.js').to_s
12
+
13
+ # @param url [String} the URL to snapshot
14
+ # @param viewport_width [Integer] the width of the screen used when
15
+ # snapshotting
16
+ # @param outfile [File] where to store the snapshot PNG.
17
+ # @param user_agent [String] an optional useragent string to used when
18
+ # requesting the page.
19
+ def initialize(url: raise, viewport_width: raise,
20
+ outfile: raise, user_agent: nil)
21
+ @viewport_width = viewport_width
22
+ @user_agent = user_agent
23
+ @outfile = outfile
24
+ @url = url
25
+ end
26
+
27
+ # Takes a snapshot of the URL and saves it in the out_file as a PNG image.
28
+ #
29
+ # @return [Hash] a hash containing the following keys:
30
+ # title [String] the <title> of the page being snapshotted
31
+ # log [String] a log of events happened during the snapshotting process
32
+ def take_snapshot!
33
+ result = {}
34
+ opts = {
35
+ address: @url,
36
+ outfile: @outfile,
37
+ viewportSize: {
38
+ width: @viewport_width,
39
+ height: @viewport_width,
40
+ },
41
+ }
42
+ opts[:userAgent] = @user_agent if @user_agent
43
+
44
+ run_phantomjs(opts) do |line|
45
+ begin
46
+ result = JSON.parse line, symbolize_names: true
47
+ rescue JSON::ParserError
48
+ # We only expect a single line of JSON to be output by our snapshot
49
+ # script. If something else is happening, it is likely a JavaScript
50
+ # error on the page and we should just forget about it and move on
51
+ # with our lives.
52
+ end
53
+ end
54
+ result
55
+ end
56
+
57
+ private
58
+
59
+ def run_phantomjs(options)
60
+ Phantomjs.run('--ignore-ssl-errors=true',
61
+ SCRIPT_PATH, options.to_json) do |line|
62
+ yield line
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,4 @@
1
+ # Defines the gem version.
2
+ module DiffuxCore
3
+ VERSION = '0.0.1'
4
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diffux-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe Lencioni
8
+ - Henric Trotzig
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oily_png
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 1.1.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 1.1.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: phantomjs
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '='
33
+ - !ruby/object:Gem::Version
34
+ version: 1.9.2.1
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '='
40
+ - !ruby/object:Gem::Version
41
+ version: 1.9.2.1
42
+ - !ruby/object:Gem::Dependency
43
+ name: diff-lcs
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 1.2.5
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 1.2.5
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.14.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 2.14.1
70
+ - !ruby/object:Gem::Dependency
71
+ name: mocha
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.0.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 1.0.0
84
+ description: Tools for taking and comparing responsive website snapshots
85
+ email:
86
+ - joe.lencioni@causes.com
87
+ - henric.trotzig@causes.com
88
+ executables:
89
+ - diffux-snapshot
90
+ - diffux-compare
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - bin/diffux-compare
95
+ - bin/diffux-snapshot
96
+ - lib/diffux_core.rb
97
+ - lib/diffux_core/diff_cluster_finder.rb
98
+ - lib/diffux_core/snapshot_comparer.rb
99
+ - lib/diffux_core/snapshot_comparison_image/after.rb
100
+ - lib/diffux_core/snapshot_comparison_image/base.rb
101
+ - lib/diffux_core/snapshot_comparison_image/before.rb
102
+ - lib/diffux_core/snapshot_comparison_image/gutter.rb
103
+ - lib/diffux_core/snapshot_comparison_image/overlayed.rb
104
+ - lib/diffux_core/snapshotter.rb
105
+ - lib/diffux_core/version.rb
106
+ homepage:
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 2.0.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.2.0
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Diffux Core
130
+ test_files: []