lib/sitediff.rb in sitediff-0.0.6 vs lib/sitediff.rb in sitediff-1.0.0

- old
+ new

@@ -1,107 +1,140 @@ #!/bin/env ruby # frozen_string_literal: true require 'sitediff/config' +require 'sitediff/diff' require 'sitediff/fetch' require 'sitediff/result' +require 'sitediff/report' require 'pathname' require 'rainbow' +require 'rubygems' require 'yaml' +# SiteDiff Object. class SiteDiff - # path to misc. static files (e.g. erb, css files) - FILES_DIR = File.join(File.dirname(__FILE__), 'sitediff', 'files') + attr_reader :config, :results - # subdirectory containing all failing diffs - DIFFS_DIR = 'diffs' + # SiteDiff installation directory. + ROOT_DIR = File.dirname(File.dirname(__FILE__)) - # files in output - FAILURES_FILE = 'failures.txt' - REPORT_FILE = 'report.html' - SETTINGS_FILE = 'settings.yaml' + # Path to misc files. Ex: *.erb, *.css. + FILES_DIR = File.join(File.dirname(__FILE__), 'sitediff', 'files') - # label will be colorized and str will not be. - # type dictates the color: can be :success, :error, or :failure - def self.log(str, type = :info, label = nil) - label = label ? "[sitediff] #{label}" : '[sitediff]' - bg = fg = nil - case type - when :info - bg = fg = nil - when :diff_success - bg = :green + # Logs a message. + # + # Label will be colorized and message will not. + # Type dictates the color: can be :success, :error, or :failure. + # + # TODO: Only print :debug messages in debug mode. + def self.log(message, type = :info, label = nil) + # Prepare label. + label ||= type unless type == :info + label = label.to_s + unless label.empty? + # Colorize label. fg = :black - when :diff_failure - bg = :red - when :warn - bg = :yellow - fg = :black - when :error - bg = :red + bg = :blue + + case type + when :info + bg = :cyan + when :success + bg = :green + when :error + bg = :red + when :warning + bg = :yellow + end + + label = '[' + label.to_s + ']' + label = Rainbow(label) + label = label.bg(bg) if bg + label = label.fg(fg) if fg + + # Add a space after the label. + label += ' ' end - label = Rainbow(label) - label = label.bg(bg) if bg - label = label.fg(fg) if fg - puts label + ' ' + str + + puts label + message end - attr_reader :config, :results + ## + # Returns the "before" site's URL. + # + # TODO: Remove in favor of config.before_url. def before @config.before['url'] end + ## + # Returns the "after" site's URL. + # + # TODO: Remove in favor of config.after_url. def after @config.after['url'] end - def initialize(config, cache, concurrency, interval, verbose = true, debug = false) + # Initialize SiteDiff. + def initialize(config, cache, verbose = true, debug = false) @cache = cache @verbose = verbose @debug = debug - @interval = interval + # Check for single-site mode validate_opts = {} if !config.before['url'] && @cache.tag?(:before) unless @cache.read_tags.include?(:before) raise SiteDiffException, "A cached 'before' is required for single-site mode" end validate_opts[:need_before] = false end config.validate(validate_opts) - - @concurrency = concurrency + # Configure diff. + Diff.diff_config(config) @config = config end - # Sanitize HTML + # Sanitize HTML. def sanitize(path, read_results) %i[before after].map do |tag| html = read_results[tag].content + # TODO: See why encoding is empty while running tests. + # + # The presence of an "encoding" value used to be used to determine + # if the sanitizer would be called. However, encoding turns up blank + # during rspec tests for some reason. encoding = read_results[tag].encoding - if encoding - config = @config.send(tag) - Sanitizer.new(html, config, path: path).sanitize + if encoding || html.length.positive? + section = @config.send(tag, true) + Sanitizer.new(html, section, path: path).sanitize else html end end end - # Process a set of read results + ## + # Process a set of read results. + # + # This is the callback that processes items fetched by the Fetcher. def process_results(path, read_results) - if (error = (read_results[:before].error || read_results[:after].error)) + error = (read_results[:before].error || read_results[:after].error) + if error diff = Result.new(path, nil, nil, nil, nil, error) else begin - diff = Result.new(path, - *sanitize(path, read_results), - read_results[:before].encoding, - read_results[:after].encoding, - nil) - rescue => e + diff = Result.new( + path, + *sanitize(path, read_results), + read_results[:before].encoding, + read_results[:after].encoding, + nil + ) + rescue StandardError => e raise if @debug Result.new(path, nil, nil, nil, nil, "Sanitization error: #{e}") end end @@ -112,62 +145,74 @@ next_diff.log(@verbose) @ordered.shift end end - # Perform the comparison, populate @results and return the number of failing - # paths (paths with non-zero diff). - def run(curl_opts = {}, debug = true) + ## + # Compute diff as per config. + # + # @return [Integer] + # Number of paths which have diffs. + def run # Map of path -> Result object, populated by process_results @results = {} @ordered = @config.paths.dup unless @cache.read_tags.empty? - SiteDiff.log('Using sites from cache: ' + - @cache.read_tags.sort.join(', ')) + SiteDiff.log('Using sites from cache: ' + @cache.read_tags.sort.join(', ')) end # TODO: Fix this after config merge refactor! # Not quite right. We are not passing @config.before or @config.after # so passing this instead but @config.after['curl_opts'] is ignored. + curl_opts = @config.setting :curl_opts config_curl_opts = @config.before['curl_opts'] curl_opts = config_curl_opts.clone.merge(curl_opts) if config_curl_opts - fetcher = Fetch.new(@cache, @config.paths, @interval, @concurrency, curl_opts, debug, - before: before, after: after) + fetcher = Fetch.new( + @cache, + @config.paths, + @config.setting(:interval), + @config.setting(:concurrency), + curl_opts, + @debug, + before: @config.before_url, + after: @config.after_url + ) + + # Run the Fetcher with "process results" as a callback. fetcher.run(&method(:process_results)) # Order by original path order - @results = @config.paths.map { |p| @results[p] } + @results = @config.paths.map { |path| @results[path] } results.map { |r| r unless r.success? }.compact.length end - # Dump results to disk - def dump(dir, report_before, report_after) - report_before ||= before - report_after ||= after - dir = Pathname.new(dir) - dir.mkpath unless dir.directory? - - # store diffs of each failing case, first wipe out existing diffs - diff_dir = dir + DIFFS_DIR - diff_dir.rmtree if diff_dir.exist? - results.each { |r| r.dump(dir) if r.status == Result::STATUS_FAILURE } - SiteDiff.log "All diff files were dumped inside #{dir.expand_path}" - - # store failing paths - failures = dir + FAILURES_FILE - SiteDiff.log "Writing failures to #{failures.expand_path}" - failures.open('w') do |f| - results.each { |r| f.puts r.path unless r.success? } + ## + # Get a reporter object to help with report generation. + def report + if @results.nil? + raise SiteDiffException( + 'No results detected. Run SiteDiff.run before SiteDiff.report.' + ) end - # create report of results - report = Diff.generate_html_report(results, report_before, report_after, - @cache) - dir.+(REPORT_FILE).open('w') { |f| f.write(report) } + Report.new(@config, @cache, @results) + end - # serve some settings - settings = { 'before' => report_before, 'after' => report_after, - 'cached' => %w[before after] } - dir.+(SETTINGS_FILE).open('w') { |f| YAML.dump(settings, f) } + ## + # Get SiteDiff gemspec. + def self.gemspec + file = ROOT_DIR + '/sitediff.gemspec' + Gem::Specification.load(file) + end + + ## + # Ensures that a directory exists and returns a Pathname for it. + # + # @param [String] dir + # path/to/directory + def self.ensure_dir(dir) + dir = Pathname.new(dir) unless dir.is_a? Pathname + dir.mkpath unless dir.directory? + dir end end