1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
# frozen_string_literal: true
require "cgi"
# Produce a simple HTML diff view.
class Diff::LCS::HTMLDiff
class << self
attr_accessor :can_expand_tabs # :nodoc:
end
self.can_expand_tabs = true
class Callbacks # :nodoc:
attr_accessor :output
attr_accessor :match_class
attr_accessor :only_a_class
attr_accessor :only_b_class
def initialize(output, options = {})
@output = output
options ||= {}
@match_class = options[:match_class] || "match"
@only_a_class = options[:only_a_class] || "only_a"
@only_b_class = options[:only_b_class] || "only_b"
end
def htmlize(element, css_class)
element = " " if element.empty?
%(<pre class="#{__send__(css_class)}">#{element}</pre>\n)
end
private :htmlize
# This will be called with both lines are the same
def match(event)
@output << htmlize(event.old_element, :match_class)
end
# This will be called when there is a line in A that isn't in B
def discard_a(event)
@output << htmlize(event.old_element, :only_a_class)
end
# This will be called when there is a line in B that isn't in A
def discard_b(event)
@output << htmlize(event.new_element, :only_b_class)
end
end
# standard:disable Style/HashSyntax
DEFAULT_OPTIONS = {
:expand_tabs => nil,
:output => nil,
:css => nil,
:title => nil
}.freeze
# standard:enable Style/HashSyntax
# standard:disable Layout/HeredocIndentation
DEFAULT_CSS = <<-CSS
body { margin: 0; }
.diff
{
border: 1px solid black;
margin: 1em 2em;
}
p
{
margin-left: 2em;
}
pre
{
padding-left: 1em;
margin: 0;
font-family: Inconsolata, Consolas, Lucida, Courier, monospaced;
white-space: pre;
}
.match { }
.only_a
{
background-color: #fdd;
color: red;
text-decoration: line-through;
}
.only_b
{
background-color: #ddf;
color: blue;
border-left: 3px solid blue
}
h1 { margin-left: 2em; }
CSS
# standard:enable Layout/HeredocIndentation
def initialize(left, right, options = nil)
@left = left
@right = right
@options = options
@options = DEFAULT_OPTIONS.dup if @options.nil?
end
def verify_options
@options[:expand_tabs] ||= 4
@options[:expand_tabs] = 4 if @options[:expand_tabs].negative?
@options[:output] ||= $stdout
@options[:css] ||= DEFAULT_CSS.dup
@options[:title] ||= "diff"
end
private :verify_options
attr_reader :options
def run
verify_options
if @options[:expand_tabs].positive? && self.class.can_expand_tabs
formatter = Text::Format.new
formatter.tabstop = @options[:expand_tabs]
@left.map! { |line| formatter.expand(line.chomp) }
@right.map! { |line| formatter.expand(line.chomp) }
end
@left.map! { |line| CGI.escapeHTML(line.chomp) }
@right.map! { |line| CGI.escapeHTML(line.chomp) }
# standard:disable Layout/HeredocIndentation
@options[:output] << <<-OUTPUT
<html>
<head>
<title>#{@options[:title]}</title>
<style type="text/css">
#{@options[:css]}
</style>
</head>
<body>
<h1>#{@options[:title]}</h1>
<p>Legend: <span class="only_a">Only in Old</span>
<span class="only_b">Only in New</span></p>
<div class="diff">
OUTPUT
# standard:enable Layout/HeredocIndentation
callbacks = Callbacks.new(@options[:output])
Diff::LCS.traverse_sequences(@left, @right, callbacks)
# standard:disable Layout/HeredocIndentation
@options[:output] << <<-OUTPUT
</div>
</body>
</html>
OUTPUT
# standard:enable Layout/HeredocIndentation
end
end
|