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 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