csvlint 1.0.0 → 1.1.0

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +4 -0
  3. data/.github/workflows/push.yml +14 -2
  4. data/.ruby-version +1 -1
  5. data/.standard_todo.yml +43 -0
  6. data/Dockerfile +16 -0
  7. data/Gemfile +2 -2
  8. data/README.md +9 -9
  9. data/Rakefile +7 -7
  10. data/csvlint.gemspec +14 -16
  11. data/docker_notes_for_windows.txt +20 -0
  12. data/features/step_definitions/cli_steps.rb +11 -11
  13. data/features/step_definitions/information_steps.rb +4 -4
  14. data/features/step_definitions/parse_csv_steps.rb +11 -11
  15. data/features/step_definitions/schema_validation_steps.rb +10 -10
  16. data/features/step_definitions/sources_steps.rb +1 -1
  17. data/features/step_definitions/validation_errors_steps.rb +19 -19
  18. data/features/step_definitions/validation_info_steps.rb +9 -9
  19. data/features/step_definitions/validation_warnings_steps.rb +11 -11
  20. data/features/support/aruba.rb +6 -6
  21. data/features/support/earl_formatter.rb +39 -39
  22. data/features/support/env.rb +10 -11
  23. data/features/support/load_tests.rb +107 -103
  24. data/features/support/webmock.rb +2 -2
  25. data/lib/csvlint/cli.rb +133 -130
  26. data/lib/csvlint/csvw/column.rb +279 -280
  27. data/lib/csvlint/csvw/date_format.rb +90 -92
  28. data/lib/csvlint/csvw/metadata_error.rb +1 -3
  29. data/lib/csvlint/csvw/number_format.rb +40 -32
  30. data/lib/csvlint/csvw/property_checker.rb +714 -717
  31. data/lib/csvlint/csvw/table.rb +49 -52
  32. data/lib/csvlint/csvw/table_group.rb +24 -23
  33. data/lib/csvlint/error_collector.rb +2 -0
  34. data/lib/csvlint/error_message.rb +0 -1
  35. data/lib/csvlint/field.rb +153 -141
  36. data/lib/csvlint/schema.rb +34 -42
  37. data/lib/csvlint/validate.rb +161 -143
  38. data/lib/csvlint/version.rb +1 -1
  39. data/lib/csvlint.rb +22 -23
  40. data/spec/csvw/column_spec.rb +15 -16
  41. data/spec/csvw/date_format_spec.rb +5 -7
  42. data/spec/csvw/number_format_spec.rb +2 -4
  43. data/spec/csvw/table_group_spec.rb +103 -105
  44. data/spec/csvw/table_spec.rb +71 -73
  45. data/spec/field_spec.rb +116 -121
  46. data/spec/schema_spec.rb +129 -139
  47. data/spec/spec_helper.rb +6 -6
  48. data/spec/validator_spec.rb +167 -190
  49. metadata +22 -55
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