sitediff 1.0.0 → 1.1.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.
@@ -20,8 +20,8 @@ class SiteDiff
20
20
  'settings' => {
21
21
  'depth' => 3,
22
22
  'interval' => 0,
23
- 'whitelist' => '',
24
- 'blacklist' => '',
23
+ 'include' => '',
24
+ 'exclude' => '',
25
25
  'concurrency' => 3,
26
26
  'preset' => nil
27
27
  },
@@ -43,6 +43,8 @@ class SiteDiff
43
43
  after_url
44
44
  ignore_whitespace
45
45
  export
46
+ output
47
+ report
46
48
  ]
47
49
 
48
50
  ##
@@ -52,8 +54,8 @@ class SiteDiff
52
54
  ALLOWED_SETTINGS_KEYS = %w[
53
55
  preset
54
56
  depth
55
- whitelist
56
- blacklist
57
+ include
58
+ exclude
57
59
  concurrency
58
60
  interval
59
61
  curl_opts
@@ -62,6 +64,8 @@ class SiteDiff
62
64
  class InvalidConfig < SiteDiffException; end
63
65
  class ConfigNotFound < SiteDiffException; end
64
66
 
67
+ attr_reader :directory
68
+
65
69
  # Takes a Hash and normalizes it to the following form by merging globals
66
70
  # into before and after. A normalized config Hash looks like this:
67
71
  #
@@ -71,6 +75,12 @@ class SiteDiff
71
75
  # before:
72
76
  # url: http://before
73
77
  # selector: body
78
+ # ## Note: use either `selector` or `regions`, but not both
79
+ # regions:
80
+ # - name: title
81
+ # selector: .field-name-title h2
82
+ # - name: body
83
+ # selector: .field-name-field-news-description .field-item
74
84
  # dom_transform:
75
85
  # - type: remove
76
86
  # selector: script
@@ -79,6 +89,13 @@ class SiteDiff
79
89
  # url: http://after
80
90
  # selector: body
81
91
  #
92
+ # ## Note: use `output` only with `regions`
93
+ # output:
94
+ # - title
95
+ # - author
96
+ # - source
97
+ # - body
98
+ #
82
99
  def self.normalize(conf)
83
100
  tools = Sanitizer::TOOLS
84
101
 
@@ -117,6 +134,7 @@ class SiteDiff
117
134
  result = {
118
135
  'before' => {},
119
136
  'after' => {},
137
+ 'output' => [],
120
138
  'settings' => {}
121
139
  }
122
140
 
@@ -149,12 +167,26 @@ class SiteDiff
149
167
  end
150
168
  end
151
169
 
170
+ # Merge output array.
171
+ result['output'] += (first['output'] || []) + (second['output'] || [])
172
+
173
+ # Merge url_report keys.
174
+ %w[before_url_report after_url_report].each do |pos|
175
+ result[pos] = first[pos] || second[pos]
176
+ end
177
+
152
178
  # Merge settings.
153
179
  result['settings'] = merge_deep(
154
180
  first['settings'] || {},
155
181
  second['settings'] || {}
156
182
  )
157
183
 
184
+ # Merge report labels.
185
+ result['report'] = merge_deep(
186
+ first['report'] || {},
187
+ second['report'] || {}
188
+ )
189
+
158
190
  result
159
191
  end
160
192
 
@@ -162,9 +194,10 @@ class SiteDiff
162
194
  # Merges 2 iterable objects deeply.
163
195
  def self.merge_deep(first, second)
164
196
  first.merge(second) do |_key, val1, val2|
165
- if val1.is_a? Hash
197
+ case val1.class
198
+ when Hash
166
199
  self.class.merge_deep(val1, val2 || {})
167
- elsif val1.is_a? Array
200
+ when Array
168
201
  val1 + (val2 || [])
169
202
  else
170
203
  val2
@@ -280,6 +313,33 @@ class SiteDiff
280
313
  @config['export'] = export
281
314
  end
282
315
 
316
+ # Get output option
317
+ def output
318
+ @config['output']
319
+ end
320
+
321
+ # Set output option
322
+ def output=(output)
323
+ raise 'Output must be an Array' unless output.is_a? Array
324
+
325
+ @config['output'] = output
326
+ end
327
+
328
+ # Return report display settings.
329
+ def report
330
+ @config['report']
331
+ end
332
+
333
+ # Set crawl time for 'before'
334
+ def before_time=(time)
335
+ @config['report']['before_time'] = time
336
+ end
337
+
338
+ # Set crawl time for 'after'
339
+ def after_time=(time)
340
+ @config['report']['after_time'] = time
341
+ end
342
+
283
343
  ##
284
344
  # Writes an array of paths to a file.
285
345
  #
@@ -407,6 +467,18 @@ class SiteDiff
407
467
  @return_value
408
468
  end
409
469
 
470
+ ##
471
+ # Return merged CURL options.
472
+ def curl_opts
473
+ # We do want string keys here
474
+ bool_hash = { 'true' => true, 'false' => false }
475
+ curl_opts = UriWrapper::DEFAULT_CURL_OPTS
476
+ .clone
477
+ .merge(settings['curl_opts'] || {})
478
+ curl_opts.each { |k, v| curl_opts[k] = bool_hash.fetch(v, v) }
479
+ curl_opts
480
+ end
481
+
410
482
  private
411
483
 
412
484
  ##
@@ -14,10 +14,10 @@ class SiteDiff
14
14
  class Creator
15
15
  ##
16
16
  # Creates a Creator object.
17
- def initialize(debug, *urls)
17
+ def initialize(debug, before, after)
18
18
  @config = nil
19
- @after = urls.pop
20
- @before = urls.pop # May be nil
19
+ @before = before
20
+ @after = after
21
21
  @debug = debug
22
22
  end
23
23
 
@@ -25,15 +25,17 @@ class SiteDiff
25
25
  # Determine if we're dealing with one or two URLs.
26
26
  def roots
27
27
  @roots = { 'after' => @after }
28
- @roots['before'] = @before if @before
28
+ @roots['before'] = @before || @after
29
29
  @roots
30
30
  end
31
31
 
32
32
  ##
33
33
  # Build a config structure, return it.
34
- def create(options, &block)
34
+ def create(options)
35
35
  @config = {}
36
- @callback = block
36
+
37
+ # @callback = block
38
+
37
39
  @dir = Pathname.new(options[:directory])
38
40
 
39
41
  # Setup instance vars
@@ -17,8 +17,8 @@ class SiteDiff
17
17
  # Create a crawler with a base URL
18
18
  def initialize(hydra, base,
19
19
  interval,
20
- whitelist,
21
- blacklist,
20
+ include_regex,
21
+ exclude_regex,
22
22
  depth = DEFAULT_DEPTH,
23
23
  curl_opts = UriWrapper::DEFAULT_CURL_OPTS,
24
24
  debug = true,
@@ -27,8 +27,8 @@ class SiteDiff
27
27
  @base_uri = Addressable::URI.parse(base)
28
28
  @base = base
29
29
  @interval = interval
30
- @whitelist = whitelist
31
- @blacklist = blacklist
30
+ @include_regex = include_regex
31
+ @exclude_regex = exclude_regex
32
32
  @found = Set.new
33
33
  @callback = block
34
34
  @curl_opts = curl_opts
@@ -119,12 +119,12 @@ class SiteDiff
119
119
  u.path.start_with?(@base_uri.path)
120
120
  next unless is_sub_uri
121
121
 
122
- is_whitelisted = @whitelist.nil? ? false : @whitelist.match(u.path)
123
- is_blacklisted = @blacklist.nil? ? false : @blacklist.match(u.path)
124
- if is_blacklisted && !is_whitelisted
125
- SiteDiff.log "Ignoring blacklisted URL #{u.path}", :info
122
+ is_included = @include_regex.nil? ? false : @include_regex.match(u.path)
123
+ is_excluded = @exclude_regex.nil? ? false : @exclude_regex.match(u.path)
124
+ if is_excluded && !is_included
125
+ SiteDiff.log "Ignoring excluded URL #{u.path}", :info
126
126
  end
127
- is_whitelisted || !is_blacklisted
127
+ is_included || !is_excluded
128
128
  end
129
129
  end
130
130
  end
@@ -57,7 +57,11 @@ class SiteDiff
57
57
  ##
58
58
  # Generates an HTML report.
59
59
  # TODO: Generate the report in SiteDif::Report instead.
60
- def generate_html(results, before, after, cache, relative = false)
60
+ def generate_html(results, before, after, cache, config)
61
+ relative = config.export
62
+ report = config.report || {}
63
+ before_url_report = report['before_url_report']
64
+ after_url_report = report['after_url_report']
61
65
  erb_path = File.join(SiteDiff::FILES_DIR, 'report.html.erb')
62
66
  ERB.new(File.read(erb_path)).result(binding)
63
67
  end
@@ -23,6 +23,12 @@
23
23
  </head>
24
24
  <body class="page-overview" data-page="overview">
25
25
  <div id="layout">
26
+ <div class="container">
27
+ <div class="heading">
28
+ <h1><%= report["title"] %></h1>
29
+ <p><%= report["details"] %></p>
30
+ </div>
31
+ </div>
26
32
  <div class="container">
27
33
  <div class="statistical-info">
28
34
  <div class="changed-pages">
@@ -48,11 +54,26 @@
48
54
  <div class="site site-<%= tag %>">
49
55
  <% notes = ['base url']
50
56
  notes << 'cached' if cache.read_tags.include?(tag.to_sym) %>
51
- <h3 class="site__tag"><%= tag.capitalize %></h3>
52
- <a href="<%= eval(tag) %>" class="site__url"><%= eval(tag) %></a>
53
- <% if cache.read_tags.include?(tag.to_sym) %>
54
- (<%= 'cached' if cache.read_tags.include?(tag.to_sym) %>)
55
- <% end %>
57
+ <div>
58
+ <h3 class="site__tag"><%= tag.capitalize %></h3>
59
+ </div>
60
+ <div>
61
+ <% if display_url = report[tag + '_url_report'] %>
62
+ <a href="<%= display_url %>" class="site__url"><%= display_url %></a>
63
+ <% else %>
64
+ <a href="<%= eval(tag) %>" class="site__url"><%= eval(tag) %></a>
65
+ <% if cache.read_tags.include?(tag.to_sym) %>
66
+ (<%= 'cached' if cache.read_tags.include?(tag.to_sym) %>)
67
+ <% end %>
68
+ <% end %>
69
+ </div>
70
+ <div>
71
+ Crawled on: <%= report[tag + '_time'] %>
72
+ </div>
73
+ <div>
74
+ <%= report[tag + '_note'] %>
75
+ </div>
76
+
56
77
  </div>
57
78
  <% end %>
58
79
  </div>
@@ -112,9 +133,15 @@
112
133
  <span class="path"><%= result.path %></span>
113
134
  <div class="buttons">
114
135
  <% unless relative %>
115
- <a href="<%= result.url(:before, before, cache) %>" class="button-before" target="_blank">Before</a>
116
- <a href="<%= result.url(:after, after, cache) %>" class="button-after" target="_blank">After</a>
117
- <a href="/sidebyside<%= result.path %>" class="button-both">Both</a>
136
+ <% unless report['before_url_report'] === false %>
137
+ <a href="<%= result.url(:before, before_url_report || before, cache) %>" class="button-before" target="_blank">Before</a>
138
+ <% end %>
139
+ <% unless report['after_url_report'] === false %>
140
+ <a href="<%= result.url(:after, after_url_report || after, cache) %>" class="button-after" target="_blank">After</a>
141
+ <% end %>
142
+ <% unless report['before_url_report'] === false || report['after_url_report'] === false %>
143
+ <a href="/sidebyside<%= result.path %>" class="button-both">Both</a>
144
+ <% end %>
118
145
  <% end %>
119
146
  <% unless result.diff_url.nil? %>
120
147
  <a href="<%= result.diff_url(relative) %>" class="button button-diff">View diff</a>
@@ -1,6 +1,5 @@
1
1
  * {
2
2
  box-sizing: border-box;
3
- outline: none;
4
3
  }
5
4
 
6
5
  a {
@@ -247,3 +246,81 @@ table .status-col {
247
246
  top: 0;
248
247
  width: 100%;
249
248
  }
249
+
250
+ /*** Overlay */
251
+ .overlay {
252
+ background-color: rgba(0, 0, 0, 0.25);
253
+ height: 100vh;
254
+ left: 0;
255
+ padding: 2em;
256
+ position: fixed;
257
+ top: 0;
258
+ width: 100vw;
259
+ }
260
+
261
+ .overlay__inner {
262
+ background-color: #fff;
263
+ height: 100%;
264
+ margin: 0;
265
+ position: relative;
266
+ width: 100%;
267
+ }
268
+
269
+ .overlay header {
270
+ background-color: #f9f9fb;
271
+ border-bottom: 2px solid #eaecf3;
272
+ display: flex;
273
+ min-height: 3em;
274
+ left: 0;
275
+ position: absolute;
276
+ top: 0;
277
+ width: 100%;
278
+ }
279
+
280
+ .overlay header .prev,
281
+ .overlay header .next,
282
+ .overlay header .exit {
283
+ padding: 0.5em 0;
284
+ margin: 0.5em;
285
+ }
286
+
287
+ .overlay header .prev a,
288
+ .overlay header .next a,
289
+ .overlay header .exit a {
290
+ background-color: #eaecf3;
291
+ font-size: .9em;
292
+ padding: 0.5em;
293
+ text-decoration: none;
294
+ }
295
+
296
+ .overlay header .prev a:hover,
297
+ .overlay header .next a:hover,
298
+ .overlay header .exit a:hover {
299
+ background-color: #d2d7ef;
300
+ }
301
+
302
+ .overlay header .prev.disabled a,
303
+ .overlay header .next.disabled a,
304
+ .overlay header .exit.disabled a,
305
+ .overlay header .prev.disabled a:hover,
306
+ .overlay header .next.disabled a:hover,
307
+ .overlay header .exit.disabled a:hover {
308
+ background-color: eaecf3;
309
+ color: #9b9db8;
310
+ cursor: not-allowed;
311
+ }
312
+ .overlay header .path {
313
+ flex-grow: 1;
314
+ padding: 1em;
315
+ }
316
+
317
+ .overlay article {
318
+ background-color: #fff;
319
+ height: 100%;
320
+ width: 100%;
321
+ }
322
+
323
+ .overlay article iframe {
324
+ height: 100%;
325
+ width: 100%;
326
+ }
@@ -3,11 +3,21 @@
3
3
  * SiteDiff report behaviors.
4
4
  */
5
5
 
6
+ 'use strict';
7
+
8
+ /* global $ */
6
9
  /**
7
10
  * SiteDiff namespace.
8
11
  */
9
12
  var SiteDiff = SiteDiff || {};
10
13
 
14
+ /**
15
+ * SiteDiff global map of diffs.
16
+ */
17
+ SiteDiff.diffs = SiteDiff.diffs || {};
18
+
19
+ SiteDiff.currentDiff = -1;
20
+
11
21
  /**
12
22
  * Scrolls the document to the said position.
13
23
  *
@@ -75,23 +85,42 @@ SiteDiff.scrollToElement = function (el, options) {
75
85
  SiteDiff.init = function () {
76
86
  // On the overview page.
77
87
  switch ($(document.body).data('page')) {
78
- case 'overview':
79
- SiteDiff.initFilterForm();
80
- break;
88
+ case 'overview':
89
+ SiteDiff.initFilterForm();
90
+ break;
81
91
 
82
- case 'diff':
83
- SiteDiff.jumpToFirstDiff();
84
- // TODO: Create a prev-next mechanism.
85
- break;
92
+ case 'diff':
93
+ SiteDiff.jumpToFirstDiff();
94
+ break;
86
95
  }
87
96
  };
88
97
 
89
98
  /**
90
- * Initializes report filters.
99
+ * Initializes report filters and overlay.
91
100
  */
92
101
  SiteDiff.initFilterForm = function () {
102
+ SiteDiff.initDiffArray();
93
103
  SiteDiff.initStatusFilter();
94
104
  SiteDiff.initSearchFilter();
105
+ SiteDiff.initOverlay();
106
+ SiteDiff.initClickHandlers();
107
+ };
108
+
109
+ /**
110
+ * Initialize global diff array
111
+ *
112
+ */
113
+ SiteDiff.initDiffArray = function() {
114
+ SiteDiff.diffs = $('.button-diff').map(function (i, element) {
115
+ var $el = $(element);
116
+ $el.data('diffindex', i);
117
+ return {
118
+ diff: $el.attr('href'),
119
+ element: $el,
120
+ index: i,
121
+ path: $el.parents('.description').find('.path').text()
122
+ };
123
+ });
95
124
  };
96
125
 
97
126
  /**
@@ -102,11 +131,10 @@ SiteDiff.initStatusFilter = function () {
102
131
  .on('change', function () {
103
132
  // Get a list of applied filters.
104
133
  var appliedFilters = $('.form-item--status input:checked')
105
- .map(function () {
106
- return this.getAttribute('value');
107
- // applied.push(this.getAttribute('value'));
108
- // console.log(applied);
109
- });
134
+ .map(function () {
135
+ return this.getAttribute('value');
136
+ // applied.push(this.getAttribute('value'));
137
+ });
110
138
  // Show only matching results, hide the rest.
111
139
  $('#sitediff-report')
112
140
  .find('.sitediff-result')
@@ -155,6 +183,169 @@ SiteDiff.initSearchFilter = function () {
155
183
  });
156
184
  };
157
185
 
186
+ /**
187
+ * Set up the diff overlay to be displayed.
188
+ */
189
+ SiteDiff.initOverlay = function () {
190
+ if (SiteDiff.diffs.length <= 0) return;
191
+
192
+ // add overlay
193
+ $('body').append($(
194
+ '<div class="overlay" style="display: none;"><div class="overlay__inner"><header>' +
195
+ '<div class="path"></div>' +
196
+ '<div class="prev"><a href="#" title="Previous diff (left arrow)">< Prev</a></div>' +
197
+ '<div class="next"><a href="#" title="Next diff (right arrow)">Next ></a></div>' +
198
+ '<div class="exit"><a href="#" title="Close diff display (Esc)">Close</a></div>' +
199
+ '</header><article></article></div></div>'));
200
+ // add header click handlers
201
+ $('.overlay header .exit').click(function (event) {
202
+ event.preventDefault();
203
+ SiteDiff.destroyOverlay();
204
+ });
205
+ $('.overlay header .prev').click(function (event) {
206
+ event.preventDefault();
207
+ SiteDiff.prevDiff();
208
+ });
209
+ $('.overlay header .next').click(function (event) {
210
+ event.preventDefault();
211
+ SiteDiff.nextDiff();
212
+ });
213
+
214
+ };
215
+
216
+ /**
217
+ * Set up click handlers for all diff buttons
218
+ */
219
+ SiteDiff.initClickHandlers = function () {
220
+ SiteDiff.diffs.each( function (i, diff) {
221
+ diff.element.click({index: i}, function (event) {
222
+ event.preventDefault();
223
+ SiteDiff.openOverlay(event.data.index);
224
+ });
225
+ });
226
+ };
227
+
228
+ /**
229
+ * Set up key handlers for overlay.
230
+ */
231
+ SiteDiff.initKeyHandlers = function () {
232
+ $(document).keyup(function (event) {
233
+ switch (event.which) {
234
+ case 37:
235
+ SiteDiff.prevDiff();
236
+ break;
237
+ case 39:
238
+ SiteDiff.nextDiff();
239
+ break;
240
+ case 27:
241
+ SiteDiff.destroyOverlay();
242
+ break;
243
+ }
244
+ });
245
+ };
246
+
247
+ /**
248
+ * Remove overlay key handlers.
249
+ */
250
+ SiteDiff.removeKeyHandlers = function () {
251
+ $(document).off('keyup');
252
+ };
253
+
254
+ /**
255
+ * Open overlay for the diff identified by the `index`.
256
+ *
257
+ * @param integer index
258
+ * The index of the diff to be viewed.
259
+ */
260
+ SiteDiff.openOverlay = function (index) {
261
+ SiteDiff.currentDiff = index;
262
+ SiteDiff.showDiff();
263
+ SiteDiff.initKeyHandlers();
264
+ $('.overlay').fadeIn(300);
265
+ };
266
+
267
+ /**
268
+ * Create the iframe to display the current diff.
269
+ */
270
+ SiteDiff.showDiff = function () {
271
+ var diff = SiteDiff.diffs[SiteDiff.currentDiff];
272
+ var iframe = '<iframe src="' + diff.diff + '"></iframe>';
273
+ $('.overlay header .path').text(diff.path);
274
+ SiteDiff.setPrevNext();
275
+ $('.overlay article').html(iframe);
276
+ };
277
+
278
+ /**
279
+ * Hide the overlay and clean up.
280
+ */
281
+ SiteDiff.destroyOverlay = function () {
282
+ $('.overlay article').empty();
283
+ SiteDiff.removeKeyHandlers();
284
+ $('.overlay').fadeOut(300, SiteDiff.scrollToButton);
285
+ };
286
+
287
+ /**
288
+ * Display the previous diff.
289
+ */
290
+ SiteDiff.prevDiff = function () {
291
+ if (SiteDiff.currentDiff > 0) {
292
+ SiteDiff.currentDiff--;
293
+ SiteDiff.showDiff();
294
+ }
295
+ };
296
+
297
+ /**
298
+ * Display the next diff.
299
+ */
300
+ SiteDiff.nextDiff = function () {
301
+ if (SiteDiff.currentDiff < SiteDiff.diffs.length - 1) {
302
+ SiteDiff.currentDiff++;
303
+ SiteDiff.showDiff();
304
+ }
305
+ };
306
+
307
+ /**
308
+ * Enable or disable prev and next buttons based on current diff.
309
+ */
310
+ SiteDiff.setPrevNext = function () {
311
+ if (SiteDiff.currentDiff <= 0) {
312
+ // set prev disabled
313
+ $('.overlay header .prev').addClass('disabled');
314
+ }
315
+ else {
316
+ $('.overlay header .prev.disabled').removeClass('disabled');
317
+ }
318
+ if (SiteDiff.currentDiff >= SiteDiff.diffs.length - 1) {
319
+ // set next disabled
320
+ $('.overlay header .next').addClass('disabled');
321
+ }
322
+ else {
323
+ $('.overlay header .next.disabled').removeClass('disabled');
324
+ }
325
+ };
326
+
327
+ /**
328
+ * Scroll to the button associated with the current diff.
329
+ */
330
+ SiteDiff.scrollToButton = function () {
331
+ var $diffButton = SiteDiff.diffs[SiteDiff.currentDiff].element;
332
+ if (! SiteDiff.isElementVisible($diffButton)) {
333
+ SiteDiff.scrollToElement($diffButton);
334
+ }
335
+ };
336
+
337
+ /**
338
+ * Check if an element is at least partly visible.
339
+ * @param element
340
+ */
341
+ SiteDiff.isElementVisible = function (element) {
342
+ var topVisible = $(window).scrollTop();
343
+ var bottomVisible = topVisible + $(window).height();
344
+ var elemTop = $(element).offset().top;
345
+ var elemBottom = elemTop + $(element).height();
346
+ return ((elemBottom <= bottomVisible) && (elemTop >= topVisible));
347
+ };
348
+
158
349
  /**
159
350
  * Jumps to the first diff on the page.
160
351
  */