csvlint 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +4 -0
  3. data/.github/workflows/push.yml +14 -2
  4. data/.pre-commit-hooks.yaml +5 -0
  5. data/.ruby-version +1 -1
  6. data/.standard_todo.yml +43 -0
  7. data/CHANGELOG.md +84 -32
  8. data/Dockerfile +16 -0
  9. data/Gemfile +2 -2
  10. data/README.md +30 -9
  11. data/Rakefile +7 -7
  12. data/csvlint.gemspec +14 -16
  13. data/docker_notes_for_windows.txt +20 -0
  14. data/features/step_definitions/cli_steps.rb +11 -11
  15. data/features/step_definitions/information_steps.rb +4 -4
  16. data/features/step_definitions/parse_csv_steps.rb +11 -11
  17. data/features/step_definitions/schema_validation_steps.rb +10 -10
  18. data/features/step_definitions/sources_steps.rb +1 -1
  19. data/features/step_definitions/validation_errors_steps.rb +19 -19
  20. data/features/step_definitions/validation_info_steps.rb +9 -9
  21. data/features/step_definitions/validation_warnings_steps.rb +11 -11
  22. data/features/support/aruba.rb +6 -6
  23. data/features/support/earl_formatter.rb +39 -39
  24. data/features/support/env.rb +10 -11
  25. data/features/support/load_tests.rb +107 -103
  26. data/features/support/webmock.rb +2 -2
  27. data/lib/csvlint/cli.rb +133 -130
  28. data/lib/csvlint/csvw/column.rb +279 -280
  29. data/lib/csvlint/csvw/date_format.rb +90 -92
  30. data/lib/csvlint/csvw/metadata_error.rb +1 -3
  31. data/lib/csvlint/csvw/number_format.rb +40 -32
  32. data/lib/csvlint/csvw/property_checker.rb +714 -717
  33. data/lib/csvlint/csvw/table.rb +49 -52
  34. data/lib/csvlint/csvw/table_group.rb +24 -23
  35. data/lib/csvlint/error_collector.rb +2 -0
  36. data/lib/csvlint/error_message.rb +0 -1
  37. data/lib/csvlint/field.rb +153 -141
  38. data/lib/csvlint/schema.rb +34 -42
  39. data/lib/csvlint/validate.rb +161 -143
  40. data/lib/csvlint/version.rb +1 -1
  41. data/lib/csvlint.rb +22 -23
  42. data/spec/csvw/column_spec.rb +15 -16
  43. data/spec/csvw/date_format_spec.rb +5 -7
  44. data/spec/csvw/number_format_spec.rb +2 -4
  45. data/spec/csvw/table_group_spec.rb +103 -105
  46. data/spec/csvw/table_spec.rb +71 -73
  47. data/spec/field_spec.rb +116 -121
  48. data/spec/schema_spec.rb +129 -139
  49. data/spec/spec_helper.rb +6 -6
  50. data/spec/validator_spec.rb +167 -190
  51. metadata +23 -55
@@ -1,6 +1,6 @@
1
- require 'json'
2
- require 'open-uri'
3
- require 'uri'
1
+ require "json"
2
+ require "open-uri"
3
+ require "uri"
4
4
 
5
5
  BASE_URI = "https://w3c.github.io/csvw/tests/"
6
6
  BASE_PATH = File.join(File.dirname(__FILE__), "..", "fixtures", "csvw")
@@ -11,109 +11,113 @@ SCRIPT_FILE_PATH = File.join(File.dirname(__FILE__), "..", "..", "bin", "run-csv
11
11
  Dir.mkdir(BASE_PATH) unless Dir.exist?(BASE_PATH)
12
12
 
13
13
  def cache_file(filename)
14
- file = File.join(BASE_PATH, filename)
15
- uri = URI.join(BASE_URI, filename)
16
- unless File.exist?(file)
17
- if filename.include? "/"
18
- levels = filename.split("/")[0..-2]
19
- for i in 0..levels.length
20
- dir = File.join(BASE_PATH, levels[0..i].join("/"))
21
- Dir.mkdir(dir) unless Dir.exist?(dir)
22
- end
23
- end
24
- STDERR.puts("storing #{file} locally")
25
- File.open(file, 'wb') do |f|
26
- f.puts URI.open(uri, 'rb').read
27
- end
28
- end
29
- return uri, file
14
+ file = File.join(BASE_PATH, filename)
15
+ uri = URI.join(BASE_URI, filename)
16
+ unless File.exist?(file)
17
+ if filename.include? "/"
18
+ levels = filename.split("/")[0..-2]
19
+ for i in 0..levels.length
20
+ dir = File.join(BASE_PATH, levels[0..i].join("/"))
21
+ Dir.mkdir(dir) unless Dir.exist?(dir)
22
+ end
23
+ end
24
+ warn("storing #{file} locally")
25
+ File.open(file, "wb") do |f|
26
+ f.puts URI.open(uri, "rb").read
27
+ end
28
+ end
29
+ [uri, file]
30
30
  end
31
31
 
32
- File.open(SCRIPT_FILE_PATH, 'w') do |file|
33
- File.chmod(0755, SCRIPT_FILE_PATH)
34
- manifest = JSON.parse( URI.open("#{BASE_URI}manifest-validation.jsonld").read )
35
- manifest["entries"].each do |entry|
36
- type = "valid"
37
- case entry["type"]
38
- when "csvt:WarningValidationTest"
39
- type = "warnings"
40
- when "csvt:NegativeValidationTest"
41
- type = "errors"
42
- end
43
- file.puts "echo \"#{entry["id"].split("#")[-1]}: #{entry["name"].gsub("`", "'")}\""
44
- file.puts "echo \"#{type}: #{entry["comment"].gsub("\"", "\\\"").gsub("`", "'")}\""
45
- if entry["action"].end_with?(".json")
46
- file.puts "csvlint --schema=features/fixtures/csvw/#{entry["action"]}"
47
- elsif entry["option"] && entry["option"]["metadata"]
48
- file.puts "csvlint features/fixtures/csvw/#{entry["action"]} --schema=features/fixtures/csvw/#{entry["option"]["metadata"]}"
49
- else
50
- file.puts "csvlint features/fixtures/csvw/#{entry["action"]}"
51
- end
52
- file.puts "echo"
53
- end
54
- end unless File.exist? SCRIPT_FILE_PATH
32
+ unless File.exist? SCRIPT_FILE_PATH
33
+ File.open(SCRIPT_FILE_PATH, "w") do |file|
34
+ File.chmod(0o755, SCRIPT_FILE_PATH)
35
+ manifest = JSON.parse(URI.open("#{BASE_URI}manifest-validation.jsonld").read)
36
+ manifest["entries"].each do |entry|
37
+ type = "valid"
38
+ case entry["type"]
39
+ when "csvt:WarningValidationTest"
40
+ type = "warnings"
41
+ when "csvt:NegativeValidationTest"
42
+ type = "errors"
43
+ end
44
+ file.puts "echo \"#{entry["id"].split("#")[-1]}: #{entry["name"].tr("`", "'")}\""
45
+ file.puts "echo \"#{type}: #{entry["comment"].gsub("\"", "\\\"").tr("`", "'")}\""
46
+ if entry["action"].end_with?(".json")
47
+ file.puts "csvlint --schema=features/fixtures/csvw/#{entry["action"]}"
48
+ elsif entry["option"] && entry["option"]["metadata"]
49
+ file.puts "csvlint features/fixtures/csvw/#{entry["action"]} --schema=features/fixtures/csvw/#{entry["option"]["metadata"]}"
50
+ else
51
+ file.puts "csvlint features/fixtures/csvw/#{entry["action"]}"
52
+ end
53
+ file.puts "echo"
54
+ end
55
+ end
56
+ end
55
57
 
56
- File.open(VALIDATION_FEATURE_FILE_PATH, 'w') do |file|
57
- file.puts "# Auto-generated file based on standard validation CSVW tests from #{BASE_URI}manifest-validation.jsonld"
58
- file.puts ""
58
+ unless File.exist? VALIDATION_FEATURE_FILE_PATH
59
+ File.open(VALIDATION_FEATURE_FILE_PATH, "w") do |file|
60
+ file.puts "# Auto-generated file based on standard validation CSVW tests from #{BASE_URI}manifest-validation.jsonld"
61
+ file.puts ""
59
62
 
60
- manifest = JSON.parse( URI.open("#{BASE_URI}manifest-validation.jsonld").read )
63
+ manifest = JSON.parse(URI.open("#{BASE_URI}manifest-validation.jsonld").read)
61
64
 
62
- file.puts "Feature: #{manifest["label"]}"
63
- file.puts ""
65
+ file.puts "Feature: #{manifest["label"]}"
66
+ file.puts ""
64
67
 
65
- manifest["entries"].each do |entry|
66
- action_uri, action_file = cache_file(entry["action"])
67
- metadata = nil
68
- provided_files = []
69
- missing_files = []
70
- file.puts "\t# #{entry["id"]}"
71
- file.puts "\t# #{entry["comment"]}"
72
- file.puts "\tScenario: #{entry["id"]} #{entry["name"].gsub("<", "less than")}"
73
- if entry["action"].end_with?(".json")
74
- file.puts "\t\tGiven I have a metadata file called \"csvw/#{entry["action"]}\""
75
- file.puts "\t\tAnd the metadata is stored at the url \"#{action_uri}\""
76
- else
77
- file.puts "\t\tGiven I have a CSV file called \"csvw/#{entry["action"]}\""
78
- file.puts "\t\tAnd it has a Link header holding \"#{entry["httpLink"]}\"" if entry["httpLink"]
79
- file.puts "\t\tAnd it is stored at the url \"#{action_uri}\""
80
- if entry["option"] && entry["option"]["metadata"]
81
- # no need to store the file here, as it will be listed in the 'implicit' list, which all get stored
82
- metadata = URI.join(BASE_URI, entry["option"]["metadata"])
83
- file.puts "\t\tAnd I have a metadata file called \"csvw/#{entry["option"]["metadata"]}\""
84
- file.puts "\t\tAnd the metadata is stored at the url \"#{metadata}\""
85
- end
86
- provided_files << action_uri.to_s
87
- if entry["name"].include?("/.well-known/csvm")
88
- file.puts "\t\tAnd I have a file called \"w3.org/.well-known/csvm\" at the url \"https://www.w3.org/.well-known/csvm\""
89
- missing_files << "#{action_uri}.json"
90
- missing_files << URI.join(action_uri, 'csvm.json').to_s
91
- else
92
- missing_files << URI.join(action_uri, '/.well-known/csvm').to_s
93
- end
94
- missing_files << "#{action_uri}-metadata.json"
95
- missing_files << URI.join(action_uri, 'csv-metadata.json').to_s
96
- end
97
- entry["implicit"].each do |implicit|
98
- implicit_uri, implicit_file = cache_file(implicit)
99
- provided_files << implicit_uri.to_s
100
- unless implicit_uri == metadata
101
- file.puts "\t\tAnd I have a file called \"csvw/#{implicit}\" at the url \"#{implicit_uri}\""
102
- end
103
- end if entry["implicit"]
104
- missing_files.each do |uri|
105
- file.puts "\t\tAnd there is no file at the url \"#{uri}\"" unless provided_files.include? uri
106
- end
107
- file.puts "\t\tWhen I carry out CSVW validation"
108
- if entry["type"] == "csvt:WarningValidationTest"
109
- file.puts "\t\tThen there should not be errors"
110
- file.puts "\t\tAnd there should be warnings"
111
- elsif entry["type"] == "csvt:NegativeValidationTest"
112
- file.puts "\t\tThen there should be errors"
113
- else
114
- file.puts "\t\tThen there should not be errors"
115
- file.puts "\t\tAnd there should not be warnings"
116
- end
117
- file.puts "\t"
118
- end
119
- end unless File.exist? VALIDATION_FEATURE_FILE_PATH
68
+ manifest["entries"].each do |entry|
69
+ action_uri, action_file = cache_file(entry["action"])
70
+ metadata = nil
71
+ provided_files = []
72
+ missing_files = []
73
+ file.puts "\t# #{entry["id"]}"
74
+ file.puts "\t# #{entry["comment"]}"
75
+ file.puts "\tScenario: #{entry["id"]} #{entry["name"].gsub("<", "less than")}"
76
+ if entry["action"].end_with?(".json")
77
+ file.puts "\t\tGiven I have a metadata file called \"csvw/#{entry["action"]}\""
78
+ file.puts "\t\tAnd the metadata is stored at the url \"#{action_uri}\""
79
+ else
80
+ file.puts "\t\tGiven I have a CSV file called \"csvw/#{entry["action"]}\""
81
+ file.puts "\t\tAnd it has a Link header holding \"#{entry["httpLink"]}\"" if entry["httpLink"]
82
+ file.puts "\t\tAnd it is stored at the url \"#{action_uri}\""
83
+ if entry["option"] && entry["option"]["metadata"]
84
+ # no need to store the file here, as it will be listed in the 'implicit' list, which all get stored
85
+ metadata = URI.join(BASE_URI, entry["option"]["metadata"])
86
+ file.puts "\t\tAnd I have a metadata file called \"csvw/#{entry["option"]["metadata"]}\""
87
+ file.puts "\t\tAnd the metadata is stored at the url \"#{metadata}\""
88
+ end
89
+ provided_files << action_uri.to_s
90
+ if entry["name"].include?("/.well-known/csvm")
91
+ file.puts "\t\tAnd I have a file called \"w3.org/.well-known/csvm\" at the url \"https://www.w3.org/.well-known/csvm\""
92
+ missing_files << "#{action_uri}.json"
93
+ missing_files << URI.join(action_uri, "csvm.json").to_s
94
+ else
95
+ missing_files << URI.join(action_uri, "/.well-known/csvm").to_s
96
+ end
97
+ missing_files << "#{action_uri}-metadata.json"
98
+ missing_files << URI.join(action_uri, "csv-metadata.json").to_s
99
+ end
100
+ entry["implicit"]&.each do |implicit|
101
+ implicit_uri, implicit_file = cache_file(implicit)
102
+ provided_files << implicit_uri.to_s
103
+ unless implicit_uri == metadata
104
+ file.puts "\t\tAnd I have a file called \"csvw/#{implicit}\" at the url \"#{implicit_uri}\""
105
+ end
106
+ end
107
+ missing_files.each do |uri|
108
+ file.puts "\t\tAnd there is no file at the url \"#{uri}\"" unless provided_files.include? uri
109
+ end
110
+ file.puts "\t\tWhen I carry out CSVW validation"
111
+ if entry["type"] == "csvt:WarningValidationTest"
112
+ file.puts "\t\tThen there should not be errors"
113
+ file.puts "\t\tAnd there should be warnings"
114
+ elsif entry["type"] == "csvt:NegativeValidationTest"
115
+ file.puts "\t\tThen there should be errors"
116
+ else
117
+ file.puts "\t\tThen there should not be errors"
118
+ file.puts "\t\tAnd there should not be warnings"
119
+ end
120
+ file.puts "\t"
121
+ end
122
+ end
123
+ end
@@ -1,3 +1,3 @@
1
- require 'webmock/cucumber'
1
+ require "webmock/cucumber"
2
2
 
3
- WebMock.disable_net_connect!(allow: %r{csvw/tests})
3
+ WebMock.disable_net_connect!(allow: %r{csvw/tests})
data/lib/csvlint/cli.rb CHANGED
@@ -1,15 +1,13 @@
1
- require 'csvlint'
2
- require 'rainbow'
3
- require 'active_support/json'
4
- require 'json'
5
- require 'pp'
6
- require 'thor'
1
+ require "csvlint"
2
+ require "rainbow"
3
+ require "active_support/json"
4
+ require "json"
5
+ require "thor"
7
6
 
8
- require 'active_support/inflector'
7
+ require "active_support/inflector"
9
8
 
10
9
  module Csvlint
11
10
  class Cli < Thor
12
-
13
11
  desc "myfile.csv OR csvlint http://example.com/myfile.csv", "Supports validating CSV files to check their syntax and contents"
14
12
 
15
13
  option :dump_errors, desc: "Pretty print error and warning objects.", type: :boolean, aliases: :d
@@ -36,162 +34,167 @@ module Csvlint
36
34
 
37
35
  private
38
36
 
39
- def read_source(source)
40
- if source.nil?
41
- # If no source is present, try reading from stdin
42
- if !$stdin.tty?
43
- source = StringIO.new(STDIN.read) rescue nil
44
- return_error "No CSV data to validate" if !options[:schema] && source.nil?
37
+ def read_source(source)
38
+ if source.nil?
39
+ # If no source is present, try reading from stdin
40
+ if !$stdin.tty?
41
+ source = begin
42
+ StringIO.new($stdin.read)
43
+ rescue
44
+ nil
45
45
  end
46
- else
47
- # If the source isn't a URL, it's a file
48
- unless source =~ /^http(s)?/
49
- begin
50
- source = File.new( source )
51
- rescue Errno::ENOENT
52
- return_error "#{source} not found"
53
- end
46
+ return_error "No CSV data to validate" if !options[:schema] && source.nil?
47
+ end
48
+ else
49
+ # If the source isn't a URL, it's a file
50
+ unless /^http(s)?/.match?(source)
51
+ begin
52
+ source = File.new(source)
53
+ rescue Errno::ENOENT
54
+ return_error "#{source} not found"
54
55
  end
55
56
  end
56
-
57
- source
58
57
  end
59
58
 
60
- def get_schema(schema)
61
- begin
62
- schema = Csvlint::Schema.load_from_uri(schema, false)
63
- rescue Csvlint::Csvw::MetadataError => e
64
- return_error "invalid metadata: #{e.message}#{" at " + e.path if e.path}"
65
- rescue OpenURI::HTTPError, Errno::ENOENT
66
- return_error "#{options[:schema]} not found"
67
- end
59
+ source
60
+ end
68
61
 
69
- if schema.class == Csvlint::Schema && schema.description == "malformed"
70
- return_error "invalid metadata: malformed JSON"
71
- end
62
+ def get_schema(schema)
63
+ begin
64
+ schema = Csvlint::Schema.load_from_uri(schema, false)
65
+ rescue Csvlint::Csvw::MetadataError => e
66
+ return_error "invalid metadata: #{e.message}#{" at " + e.path if e.path}"
67
+ rescue OpenURI::HTTPError, Errno::ENOENT
68
+ return_error "#{options[:schema]} not found"
69
+ end
72
70
 
73
- schema
71
+ if schema.instance_of?(Csvlint::Schema) && schema.description == "malformed"
72
+ return_error "invalid metadata: malformed JSON"
74
73
  end
75
74
 
76
- def fetch_schema_tables(schema, options)
77
- valid = true
75
+ schema
76
+ end
78
77
 
79
- unless schema.instance_of? Csvlint::Csvw::TableGroup
80
- return_error "No CSV data to validate."
81
- end
82
- schema.tables.keys.each do |source|
78
+ def fetch_schema_tables(schema, options)
79
+ valid = true
80
+
81
+ unless schema.instance_of? Csvlint::Csvw::TableGroup
82
+ return_error "No CSV data to validate."
83
+ end
84
+ schema.tables.keys.each do |source|
85
+ unless /^http(s)?/.match?(source)
83
86
  begin
84
- source = source.sub("file:","")
85
- source = File.new( source )
87
+ source = source.sub("file:", "")
88
+ source = File.new(source)
86
89
  rescue Errno::ENOENT
87
90
  return_error "#{source} not found"
88
- end unless source =~ /^http(s)?/
89
- valid &= validate_csv(source, schema, options[:dump_errors], nil, options[:werror])
90
- end
91
-
92
- exit 1 unless valid
93
- end
94
-
95
- def print_error(index, error, dump, color)
96
- location = ""
97
- location += error.row.to_s if error.row
98
- location += "#{error.row ? "," : ""}#{error.column.to_s}" if error.column
99
- if error.row || error.column
100
- location = "#{error.row ? "Row" : "Column"}: #{location}"
101
- end
102
- output_string = "#{index+1}. "
103
- if error.column && @schema && @schema.class == Csvlint::Schema
104
- if @schema.fields[error.column - 1] != nil
105
- output_string += "#{@schema.fields[error.column - 1].name}: "
106
91
  end
107
92
  end
108
- output_string += "#{error.type}"
109
- output_string += ". #{location}" unless location.empty?
110
- output_string += ". #{error.content}" if error.content
93
+ valid &= validate_csv(source, schema, options[:dump_errors], nil, options[:werror])
94
+ end
111
95
 
112
- puts Rainbow(output_string).color(color)
96
+ exit 1 unless valid
97
+ end
113
98
 
114
- if dump
115
- pp error
99
+ def print_error(index, error, dump, color)
100
+ location = ""
101
+ location += error.row.to_s if error.row
102
+ location += "#{error.row ? "," : ""}#{error.column}" if error.column
103
+ if error.row || error.column
104
+ location = "#{error.row ? "Row" : "Column"}: #{location}"
105
+ end
106
+ output_string = "#{index + 1}. "
107
+ if error.column && @schema && @schema.instance_of?(Csvlint::Schema)
108
+ if @schema.fields[error.column - 1] != nil
109
+ output_string += "#{@schema.fields[error.column - 1].name}: "
116
110
  end
117
111
  end
112
+ output_string += error.type.to_s
113
+ output_string += ". #{location}" unless location.empty?
114
+ output_string += ". #{error.content}" if error.content
118
115
 
119
- def print_errors(errors, dump)
120
- if errors.size > 0
121
- errors.each_with_index { |error, i| print_error(i, error, dump, :red) }
122
- end
116
+ puts Rainbow(output_string).color(color)
117
+
118
+ if dump
119
+ pp error
123
120
  end
121
+ end
124
122
 
125
- def return_error(message)
126
- puts Rainbow(message).red
127
- exit 1
123
+ def print_errors(errors, dump)
124
+ if errors.size > 0
125
+ errors.each_with_index { |error, i| print_error(i, error, dump, :red) }
128
126
  end
127
+ end
129
128
 
130
- def validate_csv(source, schema, dump, json, werror)
131
- @error_count = 0
129
+ def return_error(message)
130
+ puts Rainbow(message).red
131
+ exit 1
132
+ end
132
133
 
133
- if json === true
134
- validator = Csvlint::Validator.new( source, {}, schema )
135
- else
136
- validator = Csvlint::Validator.new( source, {}, schema, { lambda: report_lines } )
137
- end
134
+ def validate_csv(source, schema, dump, json, werror)
135
+ @error_count = 0
138
136
 
139
- if source.class == String
140
- csv = source
141
- elsif source.class == File
142
- csv = source.path
143
- else
144
- csv = "CSV"
145
- end
137
+ validator = if json === true
138
+ Csvlint::Validator.new(source, {}, schema)
139
+ else
140
+ Csvlint::Validator.new(source, {}, schema, {lambda: report_lines})
141
+ end
146
142
 
147
- if json === true
148
- json = {
149
- validation: {
150
- state: validator.valid? ? "valid" : "invalid",
151
- errors: validator.errors.map { |v| hashify(v) },
152
- warnings: validator.warnings.map { |v| hashify(v) },
153
- info: validator.info_messages.map { |v| hashify(v) },
154
- }
155
- }.to_json
156
- print json
157
- else
158
- puts "\r\n#{csv} is #{validator.valid? ? Rainbow("VALID").green : Rainbow("INVALID").red}"
159
- print_errors(validator.errors, dump)
160
- print_errors(validator.warnings, dump)
161
- end
143
+ csv = if source.instance_of?(String)
144
+ source
145
+ elsif source.instance_of?(File)
146
+ source.path
147
+ else
148
+ "CSV"
149
+ end
162
150
 
163
- return false if werror && validator.warnings.size > 0
164
- return validator.valid?
151
+ if json === true
152
+ json = {
153
+ validation: {
154
+ state: validator.valid? ? "valid" : "invalid",
155
+ errors: validator.errors.map { |v| hashify(v) },
156
+ warnings: validator.warnings.map { |v| hashify(v) },
157
+ info: validator.info_messages.map { |v| hashify(v) }
158
+ }
159
+ }.to_json
160
+ print json
161
+ else
162
+ puts "\r\n#{csv} is #{validator.valid? ? Rainbow("VALID").green : Rainbow("INVALID").red}"
163
+ print_errors(validator.errors, dump)
164
+ print_errors(validator.warnings, dump)
165
165
  end
166
166
 
167
- def hashify(error)
168
- h = {
169
- type: error.type,
170
- category: error.category,
171
- row: error.row,
172
- col: error.column
173
- }
174
-
175
- if error.column && @schema && @schema.class == Csvlint::Schema && @schema.fields[error.column - 1] != nil
176
- field = @schema.fields[error.column - 1]
177
- h[:header] = field.name
178
- h[:constraints] = Hash[field.constraints.map {|k,v| [k.underscore, v] }]
179
- end
167
+ return false if werror && validator.warnings.size > 0
168
+ validator.valid?
169
+ end
180
170
 
181
- h
171
+ def hashify(error)
172
+ h = {
173
+ type: error.type,
174
+ category: error.category,
175
+ row: error.row,
176
+ col: error.column
177
+ }
178
+
179
+ if error.column && @schema && @schema.instance_of?(Csvlint::Schema) && @schema.fields[error.column - 1] != nil
180
+ field = @schema.fields[error.column - 1]
181
+ h[:header] = field.name
182
+ h[:constraints] = field.constraints.map { |k, v| [k.underscore, v] }.to_h
182
183
  end
183
184
 
184
- def report_lines
185
- lambda do |row|
186
- new_errors = row.errors.count
187
- if new_errors > @error_count
188
- print Rainbow("!").red
189
- else
190
- print Rainbow(".").green
191
- end
192
- @error_count = new_errors
185
+ h
186
+ end
187
+
188
+ def report_lines
189
+ lambda do |row|
190
+ new_errors = row.errors.count
191
+ if new_errors > @error_count
192
+ print Rainbow("!").red
193
+ else
194
+ print Rainbow(".").green
193
195
  end
196
+ @error_count = new_errors
194
197
  end
195
-
198
+ end
196
199
  end
197
200
  end