csv-diff-report 0.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/lib/csv-diff-report/cli.rb +3 -1
- data/lib/csv-diff-report/excel.rb +22 -10
- data/lib/csv-diff-report/html.rb +11 -4
- data/lib/csv-diff-report/report.rb +78 -46
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NmNhMzlmNDc4ZGQ5NWU1MjdlMjdmN2JlNTI4NGQ0NWE1ODY0ZmVlMw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NjY0ODgxMmU1ZDFkZWQ2ZmY4MWQwNDU5NmM5YTUzNGJlODNmNzVkNw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTFiNmQ5YWZkNDllNmI0OGE3ZjI0ZjE2ZTYwY2U4YTUwZDliZDUzYTc1Njg1
|
10
|
+
NzBmOTg1YmFmMTkxY2VmNTdkNTliZTA0YTgzODJhMDZhOTFmN2U2MTlkZmQ1
|
11
|
+
MWEzMDcxM2U1NTZhNDVhMjgwYWI0MDNjNjA0NGRiOTZkMGUxNGY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Y2Y4YzVjMzY1NGJlZTE0MTkzOGQ2YWEwODU1MzVmYjJmYzNhMWIxYTJjNDBh
|
14
|
+
NDQ5NTE1NTA0MjkyYmY0YTkxMmQ5NGUyYTkxYTE3NTQ5YzEzODJjMWI3NTJj
|
15
|
+
MzQ1ZTQyMmI1NzcxOWY3OWI4ZTNkZDYwZmE3MjkzYzEyMGVkNWE=
|
data/lib/csv-diff-report/cli.rb
CHANGED
@@ -47,6 +47,7 @@ class CSVDiff
|
|
47
47
|
flag_arg :tab_delimited, 'If true, the file is assumed to be tab-delimited rather than comma-delimited'
|
48
48
|
flag_arg :ignore_header, 'If true, the first line in each source file is ignored; ' +
|
49
49
|
'requires the use of the --field-names option to name the fields'
|
50
|
+
flag_arg :ignore_case, 'If true, field comparisons are performed without regard to case'
|
50
51
|
|
51
52
|
usage_break 'Diff Options'
|
52
53
|
keyword_arg :ignore_fields, 'The names or indexes of any fields to be ignored during the diff',
|
@@ -95,11 +96,12 @@ class CSVDiff
|
|
95
96
|
# Process a CSVDiffReport using +arguments+ to determine all options.
|
96
97
|
def process(arguments)
|
97
98
|
options = {}
|
98
|
-
exclude_args = [:from, :to, :tab_delimited]
|
99
|
+
exclude_args = [:from, :to, :tab_delimited, :ignore_case]
|
99
100
|
arguments.each_pair do |arg, val|
|
100
101
|
options[arg] = val if val && !exclude_args.include?(arg)
|
101
102
|
end
|
102
103
|
options[:csv_options] = {:col_sep => "\t"} if arguments.tab_delimited
|
104
|
+
options[:case_sensitive] = !arguments.ignore_case
|
103
105
|
rep = CSVDiff::Report.new
|
104
106
|
rep.diff(arguments.from, arguments.to, options)
|
105
107
|
|
@@ -50,11 +50,11 @@ class CSVDiff
|
|
50
50
|
xl.workbook.add_worksheet(name: 'Summary') do |sheet|
|
51
51
|
sheet.add_row do |row|
|
52
52
|
row.add_cell 'From:', :style => @xl_styles['Title']
|
53
|
-
row.add_cell compare_from
|
53
|
+
row.add_cell @diffs.size > 1 ? File.dirname(compare_from) : compare_from
|
54
54
|
end
|
55
55
|
sheet.add_row do |row|
|
56
56
|
row.add_cell 'To:', :style => @xl_styles['Title']
|
57
|
-
row.add_cell compare_to
|
57
|
+
row.add_cell @diffs.size > 1 ? File.dirname(compare_to) : compare_to
|
58
58
|
end
|
59
59
|
sheet.add_row
|
60
60
|
sheet.add_row ['Sheet', 'Adds', 'Deletes', 'Updates', 'Moves'], :style => @xl_styles['Title']
|
@@ -64,7 +64,8 @@ class CSVDiff
|
|
64
64
|
sheet.column_info.first.width = 20
|
65
65
|
|
66
66
|
@diffs.each do |file_diff|
|
67
|
-
sheet.add_row([
|
67
|
+
sheet.add_row([file_diff.options[:sheet_name] ||
|
68
|
+
File.basename(file_diff.left.path, File.extname(file_diff.left.path)),
|
68
69
|
file_diff.summary['Add'], file_diff.summary['Delete'],
|
69
70
|
file_diff.summary['Update'], file_diff.summary['Move']])
|
70
71
|
xl_diff_sheet(xl, file_diff) if file_diff.diffs.size > 0
|
@@ -76,10 +77,16 @@ class CSVDiff
|
|
76
77
|
|
77
78
|
# Add diff sheet
|
78
79
|
def xl_diff_sheet(xl, file_diff)
|
79
|
-
sheet_name =
|
80
|
-
|
80
|
+
sheet_name = file_diff.options[:sheet_name] ||
|
81
|
+
File.basename(file_diff.left.path, File.extname(file_diff.left.path))
|
82
|
+
all_fields = [:row, :action]
|
83
|
+
all_fields << :sibling_position unless file_diff.options[:ignore_moves]
|
84
|
+
freeze_cols = file_diff.options[:freeze_cols] ||
|
85
|
+
(all_fields.length + file_diff.left.key_fields.max + 1)
|
86
|
+
all_fields.concat(file_diff.diff_fields)
|
81
87
|
xl.workbook.add_worksheet(name: sheet_name) do |sheet|
|
82
|
-
sheet.add_row(all_fields.map{ |f| f.
|
88
|
+
sheet.add_row(all_fields.map{ |f| f.is_a?(Symbol) ? titleize(f) : f },
|
89
|
+
:style => @xl_styles['Title'])
|
83
90
|
file_diff.diffs.sort_by{|k, v| v[:row] }.each do |key, diff|
|
84
91
|
sheet.add_row do |row|
|
85
92
|
chg = diff[:action]
|
@@ -107,8 +114,13 @@ class CSVDiff
|
|
107
114
|
end
|
108
115
|
case new
|
109
116
|
when String
|
110
|
-
|
111
|
-
|
117
|
+
if new =~ /^0+\d+(\.\d+)?/
|
118
|
+
# Don't let Excel auto-convert this to a number, as that
|
119
|
+
# will remove the leading zero(s)
|
120
|
+
cell = row.add_cell(new, :style => style, :type => :string)
|
121
|
+
else
|
122
|
+
cell = row.add_cell(new.encode('utf-8'), :style => style)
|
123
|
+
end
|
112
124
|
else
|
113
125
|
cell = row.add_cell(new, :style => style)
|
114
126
|
end
|
@@ -120,7 +132,7 @@ class CSVDiff
|
|
120
132
|
sheet.column_info.each do |ci|
|
121
133
|
ci.width = 80 if ci.width > 80
|
122
134
|
end
|
123
|
-
xl_filter_and_freeze(sheet,
|
135
|
+
xl_filter_and_freeze(sheet, freeze_cols)
|
124
136
|
end
|
125
137
|
end
|
126
138
|
|
@@ -143,7 +155,7 @@ class CSVDiff
|
|
143
155
|
begin
|
144
156
|
xl.serialize(path)
|
145
157
|
path
|
146
|
-
rescue
|
158
|
+
rescue StandardError => ex
|
147
159
|
Console.puts ex.message, :red
|
148
160
|
raise "Unable to replace existing Excel file #{path} - is it already open in Excel?"
|
149
161
|
end
|
data/lib/csv-diff-report/html.rb
CHANGED
@@ -69,8 +69,13 @@ class CSVDiff
|
|
69
69
|
body << '<p>Source Locations:</p>'
|
70
70
|
body << '<table>'
|
71
71
|
body << '<tbody>'
|
72
|
-
|
73
|
-
|
72
|
+
if @diffs.size > 1
|
73
|
+
body << "<tr><th>From:</th><td>#{File.dirname(@left)}</td></tr>"
|
74
|
+
body << "<tr><th>To:</th><td>#{File.dirname(@right)}</td></tr>"
|
75
|
+
else
|
76
|
+
body << "<tr><th>From:</th><td>#{@left}</td></tr>"
|
77
|
+
body << "<tr><th>To:</th><td>#{@right}</td></tr>"
|
78
|
+
end
|
74
79
|
body << '</tbody>'
|
75
80
|
body << '</table>'
|
76
81
|
body << '<br>'
|
@@ -124,11 +129,13 @@ class CSVDiff
|
|
124
129
|
end
|
125
130
|
body << '</p>'
|
126
131
|
|
127
|
-
all_fields = [:row, :action
|
132
|
+
all_fields = [:row, :action]
|
133
|
+
all_fields << :sibling_position unless file_diff.options[:ignore_moves]
|
134
|
+
all_fields.concat(file_diff.diff_fields)
|
128
135
|
body << '<table>'
|
129
136
|
body << '<thead><tr>'
|
130
137
|
all_fields.each do |fld|
|
131
|
-
body << "<th>#{fld.
|
138
|
+
body << "<th>#{fld.is_a?(Symbol) ? titleize(fld) : fld}</th>"
|
132
139
|
end
|
133
140
|
body << '</tr></thead>'
|
134
141
|
body << '<tbody>'
|
@@ -14,9 +14,30 @@ class CSVDiff
|
|
14
14
|
include Html
|
15
15
|
|
16
16
|
|
17
|
-
# Instantiate a new diff report object.
|
18
|
-
|
17
|
+
# Instantiate a new diff report object. Takes an optional block callback
|
18
|
+
# to use for handling the output generated by the diff process. If no
|
19
|
+
# callback is supplied, this output will be sent to the console using
|
20
|
+
# ColorConsole.
|
21
|
+
#
|
22
|
+
# @yield [*out] If supplied, the block passed to this method will be
|
23
|
+
# called for each line of text to be output. The argument to the block
|
24
|
+
# will be an array of text chunks, each of which may be accompanied by
|
25
|
+
# optional foreground and background colours.
|
26
|
+
def initialize(&block)
|
19
27
|
@diffs = []
|
28
|
+
@echo_handler = block
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def echo(*args)
|
33
|
+
if @echo_handler
|
34
|
+
@echo_handler.call(*args)
|
35
|
+
else
|
36
|
+
args.each do |out|
|
37
|
+
Console.write(*out)
|
38
|
+
end
|
39
|
+
Console.puts
|
40
|
+
end
|
20
41
|
end
|
21
42
|
|
22
43
|
|
@@ -24,6 +45,26 @@ class CSVDiff
|
|
24
45
|
def <<(diff)
|
25
46
|
if diff.is_a?(CSVDiff)
|
26
47
|
@diffs << diff
|
48
|
+
unless @left
|
49
|
+
@left = Pathname.new(diff.left.path)
|
50
|
+
@right = Pathname.new(diff.right.path)
|
51
|
+
end
|
52
|
+
diff.diff_warnings.each{ |warn| echo warn, :yellow }
|
53
|
+
out = []
|
54
|
+
out << ["Found #{diff.diffs.size} differences"]
|
55
|
+
diff.summary.each_with_index.map do |pair, i|
|
56
|
+
out << [i == 0 ? ": " : ", "]
|
57
|
+
k, v = pair
|
58
|
+
color = case k
|
59
|
+
when 'Add' then :light_green
|
60
|
+
when 'Delete' then :red
|
61
|
+
when 'Update' then :cyan
|
62
|
+
when 'Move' then :light_magenta
|
63
|
+
when 'Warning' then :yellow
|
64
|
+
end
|
65
|
+
out << ["#{v} #{k}s", color]
|
66
|
+
end
|
67
|
+
echo(*out)
|
27
68
|
else
|
28
69
|
raise ArgumentError, "Only CSVDiff objects can be added to a CSVDiff::Report"
|
29
70
|
end
|
@@ -37,15 +78,15 @@ class CSVDiff
|
|
37
78
|
@left = Pathname.new(left)
|
38
79
|
@right = Pathname.new(right)
|
39
80
|
if @left.file? && @right.file?
|
40
|
-
|
41
|
-
|
42
|
-
|
81
|
+
echo "Performing file diff:"
|
82
|
+
echo " From File: #{@left}"
|
83
|
+
echo " To File: #{@right}"
|
43
84
|
opt_file = load_opt_file(@left.dirname)
|
44
85
|
diff_file(@left.to_s, @right.to_s, options, opt_file)
|
45
86
|
elsif @left.directory? && @right.directory?
|
46
|
-
|
47
|
-
|
48
|
-
|
87
|
+
echo "Performing directory diff:"
|
88
|
+
echo " From directory: #{@left}"
|
89
|
+
echo " To directory: #{@right}"
|
49
90
|
opt_file = load_opt_file(@left)
|
50
91
|
if fts = options[:file_types]
|
51
92
|
file_types = find_matching_file_types(fts, opt_file)
|
@@ -69,15 +110,15 @@ class CSVDiff
|
|
69
110
|
# @param format [Symbol] The output format for the report; one of :html or
|
70
111
|
# :xlsx.
|
71
112
|
def output(path, format = :html)
|
72
|
-
path = case
|
73
|
-
when /^
|
74
|
-
html_output(path)
|
75
|
-
when /^xls(x)?$/i
|
113
|
+
path = case
|
114
|
+
when format.to_s =~ /^xlsx?$/i || File.extname(path) =~ /xlsx?$/i
|
76
115
|
xl_output(path)
|
116
|
+
when format.to_s =~ /^html$/i || File.extname(path) =~ /html$/i
|
117
|
+
html_output(path)
|
77
118
|
else
|
78
119
|
raise ArgumentError, "Unrecognised output format: #{format}"
|
79
120
|
end
|
80
|
-
|
121
|
+
echo "Diff report saved to '#{path}'"
|
81
122
|
end
|
82
123
|
|
83
124
|
|
@@ -89,7 +130,7 @@ class CSVDiff
|
|
89
130
|
opt_path = Pathname(dir + '.csvdiff')
|
90
131
|
opt_path = Pathname('.csvdiff') unless opt_path.exist?
|
91
132
|
if opt_path.exist?
|
92
|
-
|
133
|
+
echo "Loading options from '#{opt_path}'"
|
93
134
|
opt_file = YAML.load(IO.read(opt_path))
|
94
135
|
symbolize_keys(opt_file)
|
95
136
|
end
|
@@ -103,6 +144,11 @@ class CSVDiff
|
|
103
144
|
end
|
104
145
|
|
105
146
|
|
147
|
+
def titleize(sym)
|
148
|
+
sym.to_s.gsub(/_/, ' ').gsub(/\b([a-z])/) { $1.upcase }
|
149
|
+
end
|
150
|
+
|
151
|
+
|
106
152
|
# Locates the file types in +opt_file+ that match the +file_types+ list of
|
107
153
|
# file type names or patterns
|
108
154
|
def find_matching_file_types(file_types, opt_file)
|
@@ -114,15 +160,15 @@ class CSVDiff
|
|
114
160
|
if matches.size > 0
|
115
161
|
matched_fts.concat(matches)
|
116
162
|
else
|
117
|
-
|
118
|
-
|
163
|
+
echo "No file type matching '#{ft}' defined in .csvdiff", :yellow
|
164
|
+
echo "Known file types are: #{opt_file[:file_types].keys.join(', ')}", :yellow
|
119
165
|
end
|
120
166
|
end
|
121
167
|
else
|
122
168
|
if opt_file
|
123
|
-
|
169
|
+
echo "No file types are defined in .csvdiff", :yellow
|
124
170
|
else
|
125
|
-
|
171
|
+
echo "The file_types option can only be used when a " +
|
126
172
|
".csvdiff is present in the LEFT or current directory", :yellow
|
127
173
|
end
|
128
174
|
end
|
@@ -135,18 +181,17 @@ class CSVDiff
|
|
135
181
|
pattern = Pathname(options[:pattern] || '*')
|
136
182
|
exclude = options[:exclude]
|
137
183
|
|
138
|
-
|
139
|
-
|
184
|
+
echo " Include Pattern: #{pattern}"
|
185
|
+
echo " Exclude Pattern: #{exclude}" if exclude
|
140
186
|
|
141
|
-
|
142
|
-
|
143
|
-
excludes = exclude ? Dir[left + exclude] : []
|
187
|
+
left_files = Dir[(left + pattern).to_s.gsub('\\', '/')].sort
|
188
|
+
excludes = exclude ? Dir[(left + exclude).to_s.gsub('\\', '/')] : []
|
144
189
|
(left_files - excludes).each_with_index do |file, i|
|
145
190
|
right_file = right + File.basename(file)
|
146
191
|
if right_file.file?
|
147
192
|
diff_file(file, right_file.to_s, options, opt_file)
|
148
193
|
else
|
149
|
-
|
194
|
+
echo "Skipping file '#{File.basename(file)}', as there is " +
|
150
195
|
"no corresponding TO file", :yellow
|
151
196
|
end
|
152
197
|
end
|
@@ -164,23 +209,9 @@ class CSVDiff
|
|
164
209
|
options = settings.merge(options)
|
165
210
|
from = open_source(left, :from, options)
|
166
211
|
to = open_source(right, :to, options)
|
167
|
-
diff = CSVDiff.new(
|
168
|
-
diff.diff_warnings.each{ |warn| Console.puts warn, :yellow }
|
169
|
-
Console.write "Found #{diff.diffs.size} differences"
|
170
|
-
diff.summary.each_with_index.map do |pair, i|
|
171
|
-
Console.write i == 0 ? ": " : ", "
|
172
|
-
k, v = pair
|
173
|
-
color = case k
|
174
|
-
when 'Add' then :light_green
|
175
|
-
when 'Delete' then :red
|
176
|
-
when 'Update' then :cyan
|
177
|
-
when 'Move' then :light_magenta
|
178
|
-
when 'Warning' then :yellow
|
179
|
-
end
|
180
|
-
Console.write "#{v} #{k}s", color
|
181
|
-
end
|
182
|
-
Console.puts
|
212
|
+
diff = CSVDiff.new(from, to, options)
|
183
213
|
self << diff
|
214
|
+
diff
|
184
215
|
end
|
185
216
|
|
186
217
|
|
@@ -190,14 +221,14 @@ class CSVDiff
|
|
190
221
|
settings = opt_file && opt_file[:defaults] || {}
|
191
222
|
opt_file && opt_file[:file_types] && opt_file[:file_types].each do |file_type, hsh|
|
192
223
|
unless hsh[:pattern]
|
193
|
-
|
224
|
+
echo "Invalid setting for file_type #{file_type} in .csvdiff; " +
|
194
225
|
"missing a 'pattern' key to use to match files", :yellow
|
195
226
|
hsh[:pattern] = '-'
|
196
227
|
end
|
197
228
|
next if hsh[:pattern] == '-'
|
198
229
|
unless hsh[:matched_files]
|
199
|
-
hsh[:matched_files] = Dir[(left.dirname + hsh[:pattern]).to_s]
|
200
|
-
hsh[:matched_files] -= Dir[(left.dirname + hsh[:exclude]).to_s] if hsh[:exclude]
|
230
|
+
hsh[:matched_files] = Dir[(left.dirname + hsh[:pattern]).to_s.gsub('\\', '/')]
|
231
|
+
hsh[:matched_files] -= Dir[(left.dirname + hsh[:exclude]).to_s.gsub('\\', '/')] if hsh[:exclude]
|
201
232
|
end
|
202
233
|
if hsh[:matched_files].include?(left.to_s)
|
203
234
|
settings.merge!(hsh)
|
@@ -214,10 +245,11 @@ class CSVDiff
|
|
214
245
|
# @param src [String] A path to the file to be opened.
|
215
246
|
# @param options [Hash] An options hash to be passed to CSVSource.
|
216
247
|
def open_source(src, left_right, options)
|
217
|
-
|
248
|
+
out = ["Opening #{left_right.to_s.upcase} file '#{File.basename(src)}'..."]
|
218
249
|
csv_src = CSVDiff::CSVSource.new(src.to_s, options)
|
219
|
-
|
220
|
-
|
250
|
+
out << [" #{csv_src.lines.size} lines read", :white]
|
251
|
+
echo(*out)
|
252
|
+
csv_src.warnings.each{ |warn| echo warn, :yellow }
|
221
253
|
csv_src
|
222
254
|
end
|
223
255
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv-diff-report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Gardiner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: csv-diff
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ! '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ! '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: arg-parser
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ! '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
40
|
+
version: '0.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: color-console
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|