csv-diff-report 0.2 → 0.3.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.
- 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
|