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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTAxMGE1NTA2MjRhMjkzOGUyNThmMGQ1MjJmMGUwY2FjOWUzNzg2OQ==
4
+ NmNhMzlmNDc4ZGQ5NWU1MjdlMjdmN2JlNTI4NGQ0NWE1ODY0ZmVlMw==
5
5
  data.tar.gz: !binary |-
6
- NDdhNTE0M2M5MWFmNDUyYTE4NTgyMWIzMWY0NWNkOTYwMGFhM2FmNA==
6
+ NjY0ODgxMmU1ZDFkZWQ2ZmY4MWQwNDU5NmM5YTUzNGJlODNmNzVkNw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YjU2ZGZjMTJlOTUzYzE1YTE5MmZiOTZkYTRhZjBhOWVmODRiMDlhMDI5ZDZh
10
- ZTUzYjUxYzNlOGYzMDZiYTgzNjU1YTE2ZDkzZTk4Mjk1ZTQxMmI2MzAyYzM1
11
- OGFmOWEzMTVjZDM0NThlYmExMWJlZjQwZmZkNWVkYWU1Nzk3ZTQ=
9
+ YTFiNmQ5YWZkNDllNmI0OGE3ZjI0ZjE2ZTYwY2U4YTUwZDliZDUzYTc1Njg1
10
+ NzBmOTg1YmFmMTkxY2VmNTdkNTliZTA0YTgzODJhMDZhOTFmN2U2MTlkZmQ1
11
+ MWEzMDcxM2U1NTZhNDVhMjgwYWI0MDNjNjA0NGRiOTZkMGUxNGY=
12
12
  data.tar.gz: !binary |-
13
- N2I3NjgzYjQ0YTc5OTJkOTY2YWM3MTQyMDdiZjJiODdmNTQ5MDQ2MjBjZTYx
14
- MjRhMDBiMTY1NDE0ZjI1YTE2NTUwZWFjZDcyMDFiMDg1MzNkMmU3MjVkNDBi
15
- MDZkMWY2Yzk4YmVlYjZlMWNiZDQyYzIyMTg5MjA3MzJlODYxZmQ=
13
+ Y2Y4YzVjMzY1NGJlZTE0MTkzOGQ2YWEwODU1MzVmYjJmYzNhMWIxYTJjNDBh
14
+ NDQ5NTE1NTA0MjkyYmY0YTkxMmQ5NGUyYTkxYTE3NTQ5YzEzODJjMWI3NTJj
15
+ MzQ1ZTQyMmI1NzcxOWY3OWI4ZTNkZDYwZmE3MjkzYzEyMGVkNWE=
@@ -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([File.basename(file_diff.left.path, File.extname(file_diff.left.path)),
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 = File.basename(file_diff.left.path, File.extname(file_diff.left.path))
80
- all_fields = [:row, :action, :sibling_position] + file_diff.diff_fields
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.to_s }, :style => @xl_styles['Title'])
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
- cell = row.add_cell(new.encode('utf-8'), :style => style) #, :type => :string)
111
- # cell = row.add_cell(new, :style => style)
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, 5)
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 RuntimeError => ex
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
@@ -69,8 +69,13 @@ class CSVDiff
69
69
  body << '<p>Source Locations:</p>'
70
70
  body << '<table>'
71
71
  body << '<tbody>'
72
- body << "<tr><th>From:</th><td>#{@left}</td></tr>"
73
- body << "<tr><th>To:</th><td>#{@right}</td></tr>"
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, :sibling_position] + file_diff.diff_fields
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.to_s}</th>"
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
- def initialize
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
- Console.puts "Performing file diff:"
41
- Console.puts " From File: #{@left}"
42
- Console.puts " To File: #{@right}"
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
- Console.puts "Performing directory diff:"
47
- Console.puts " From directory: #{@left}"
48
- Console.puts " To directory: #{@right}"
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 format.to_s
73
- when /^html$/i
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
- Console.puts "Diff report saved to '#{path}'"
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
- Console.puts "Loading options from .csvdiff at '#{dir}'"
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
- Console.puts "No file type matching '#{ft}' defined in .csvdiff", :yellow
118
- Console.puts "Known file types are: #{opt_file[:file_types].keys.join(', ')}", :yellow
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
- Console.puts "No file types are defined in .csvdiff", :yellow
169
+ echo "No file types are defined in .csvdiff", :yellow
124
170
  else
125
- Console.puts "The file_types option can only be used when a " +
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
- Console.puts " Include Pattern: #{pattern}"
139
- Console.puts " Exclude Pattern: #{exclude}" if exclude
184
+ echo " Include Pattern: #{pattern}"
185
+ echo " Exclude Pattern: #{exclude}" if exclude
140
186
 
141
-
142
- left_files = Dir[left + pattern].sort
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
- Console.puts "Skipping file '#{File.basename(file)}', as there is " +
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(left, right, options)
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
- Console.puts "Invalid setting for file_type #{file_type} in .csvdiff; " +
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
- Console.write "Opening #{left_right.to_s.upcase} file '#{File.basename(src)}'..."
248
+ out = ["Opening #{left_right.to_s.upcase} file '#{File.basename(src)}'..."]
218
249
  csv_src = CSVDiff::CSVSource.new(src.to_s, options)
219
- Console.puts " #{csv_src.lines.size} lines read", :white
220
- csv_src.warnings.each{ |warn| Console.puts warn, :yellow }
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: '0.2'
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: 2014-08-11 00:00:00.000000000 Z
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.1'
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.1'
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.1'
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.1'
40
+ version: '0.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: color-console
43
43
  requirement: !ruby/object:Gem::Requirement