sitediff 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  */