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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +4 -0
- data/.github/workflows/push.yml +14 -2
- data/.ruby-version +1 -1
- data/.standard_todo.yml +43 -0
- data/Dockerfile +16 -0
- data/Gemfile +2 -2
- data/README.md +9 -9
- data/Rakefile +7 -7
- data/csvlint.gemspec +14 -16
- data/docker_notes_for_windows.txt +20 -0
- data/features/step_definitions/cli_steps.rb +11 -11
- data/features/step_definitions/information_steps.rb +4 -4
- data/features/step_definitions/parse_csv_steps.rb +11 -11
- data/features/step_definitions/schema_validation_steps.rb +10 -10
- data/features/step_definitions/sources_steps.rb +1 -1
- data/features/step_definitions/validation_errors_steps.rb +19 -19
- data/features/step_definitions/validation_info_steps.rb +9 -9
- data/features/step_definitions/validation_warnings_steps.rb +11 -11
- data/features/support/aruba.rb +6 -6
- data/features/support/earl_formatter.rb +39 -39
- data/features/support/env.rb +10 -11
- data/features/support/load_tests.rb +107 -103
- data/features/support/webmock.rb +2 -2
- data/lib/csvlint/cli.rb +133 -130
- data/lib/csvlint/csvw/column.rb +279 -280
- data/lib/csvlint/csvw/date_format.rb +90 -92
- data/lib/csvlint/csvw/metadata_error.rb +1 -3
- data/lib/csvlint/csvw/number_format.rb +40 -32
- data/lib/csvlint/csvw/property_checker.rb +714 -717
- data/lib/csvlint/csvw/table.rb +49 -52
- data/lib/csvlint/csvw/table_group.rb +24 -23
- data/lib/csvlint/error_collector.rb +2 -0
- data/lib/csvlint/error_message.rb +0 -1
- data/lib/csvlint/field.rb +153 -141
- data/lib/csvlint/schema.rb +34 -42
- data/lib/csvlint/validate.rb +161 -143
- data/lib/csvlint/version.rb +1 -1
- data/lib/csvlint.rb +22 -23
- data/spec/csvw/column_spec.rb +15 -16
- data/spec/csvw/date_format_spec.rb +5 -7
- data/spec/csvw/number_format_spec.rb +2 -4
- data/spec/csvw/table_group_spec.rb +103 -105
- data/spec/csvw/table_spec.rb +71 -73
- data/spec/field_spec.rb +116 -121
- data/spec/schema_spec.rb +129 -139
- data/spec/spec_helper.rb +6 -6
- data/spec/validator_spec.rb +167 -190
- metadata +22 -55
data/features/support/aruba.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "aruba"
|
2
|
+
require "aruba/cucumber"
|
3
3
|
|
4
|
-
require
|
4
|
+
require "csvlint/cli"
|
5
5
|
|
6
6
|
module Csvlint
|
7
7
|
class CliRunner
|
8
8
|
# Allow everything fun to be injected from the outside while defaulting to normal implementations.
|
9
|
-
def initialize(argv, stdin =
|
9
|
+
def initialize(argv, stdin = $stdin, stdout = $stdout, stderr = $stderr, kernel = Kernel)
|
10
10
|
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
11
11
|
end
|
12
12
|
|
@@ -22,11 +22,11 @@ module Csvlint
|
|
22
22
|
|
23
23
|
# Thor::Base#start does not have a return value, assume success if no exception is raised.
|
24
24
|
0
|
25
|
-
rescue
|
25
|
+
rescue => e
|
26
26
|
# The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
|
27
27
|
b = e.backtrace
|
28
28
|
@stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
|
29
|
-
@stderr.puts(b.map{|s| "\tfrom #{s}"}.join("\n"))
|
29
|
+
@stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
|
30
30
|
1
|
31
31
|
rescue SystemExit => e
|
32
32
|
e.status
|
@@ -1,33 +1,33 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "rdf"
|
2
|
+
require "rdf/turtle"
|
3
3
|
|
4
4
|
class EarlFormatter
|
5
5
|
def initialize(step_mother, io, options)
|
6
6
|
output = RDF::Resource.new("")
|
7
7
|
@graph = RDF::Graph.new
|
8
|
-
@graph << [
|
9
|
-
@graph << [
|
10
|
-
@graph << [
|
11
|
-
@graph << [
|
12
|
-
@graph << [
|
13
|
-
@graph << [
|
14
|
-
@graph << [
|
15
|
-
@graph << [
|
16
|
-
@graph << [
|
17
|
-
@graph << [
|
18
|
-
@graph << [
|
19
|
-
@graph << [
|
20
|
-
@graph << [
|
21
|
-
@graph << [
|
22
|
-
@graph << [
|
23
|
-
@graph << [
|
24
|
-
@graph << [
|
25
|
-
@graph << [
|
26
|
-
@graph << [
|
27
|
-
@graph << [
|
28
|
-
@graph << [
|
29
|
-
@graph << [
|
30
|
-
@graph << [
|
8
|
+
@graph << [CSVLINT, RDF.type, RDF::DOAP.Project]
|
9
|
+
@graph << [CSVLINT, RDF.type, EARL.TestSubject]
|
10
|
+
@graph << [CSVLINT, RDF.type, EARL.Software]
|
11
|
+
@graph << [CSVLINT, RDF::DOAP.name, "csvlint"]
|
12
|
+
@graph << [CSVLINT, RDF::DC.title, "csvlint"]
|
13
|
+
@graph << [CSVLINT, RDF::DOAP.description, "CSV validator"]
|
14
|
+
@graph << [CSVLINT, RDF::DOAP.homepage, RDF::Resource.new("https://github.com/theodi/csvlint.rb")]
|
15
|
+
@graph << [CSVLINT, RDF::DOAP.license, RDF::Resource.new("https://raw.githubusercontent.com/theodi/csvlint.rb/master/LICENSE.md")]
|
16
|
+
@graph << [CSVLINT, RDF::DOAP["programming-language"], "Ruby"]
|
17
|
+
@graph << [CSVLINT, RDF::DOAP.implements, RDF::Resource.new("http://www.w3.org/TR/tabular-data-model/")]
|
18
|
+
@graph << [CSVLINT, RDF::DOAP.implements, RDF::Resource.new("http://www.w3.org/TR/tabular-metadata/")]
|
19
|
+
@graph << [CSVLINT, RDF::DOAP.developer, ODI]
|
20
|
+
@graph << [CSVLINT, RDF::DOAP.maintainer, ODI]
|
21
|
+
@graph << [CSVLINT, RDF::DOAP.documenter, ODI]
|
22
|
+
@graph << [CSVLINT, RDF::FOAF.maker, ODI]
|
23
|
+
@graph << [CSVLINT, RDF::DC.creator, ODI]
|
24
|
+
@graph << [output, RDF::FOAF["primaryTopic"], CSVLINT]
|
25
|
+
@graph << [output, RDF::DC.issued, DateTime.now]
|
26
|
+
@graph << [output, RDF::FOAF.maker, ODI]
|
27
|
+
@graph << [ODI, RDF.type, RDF::FOAF.Organization]
|
28
|
+
@graph << [ODI, RDF.type, EARL.Assertor]
|
29
|
+
@graph << [ODI, RDF::FOAF.name, "Open Data Institute"]
|
30
|
+
@graph << [ODI, RDF::FOAF.homepage, "https://theodi.org/"]
|
31
31
|
end
|
32
32
|
|
33
33
|
def scenario_name(keyword, name, file_colon_line, source_indent)
|
@@ -40,27 +40,27 @@ class EarlFormatter
|
|
40
40
|
passed = false unless s.status == :passed
|
41
41
|
end
|
42
42
|
a = RDF::Node.new
|
43
|
-
@graph << [
|
44
|
-
@graph << [
|
45
|
-
@graph << [
|
46
|
-
@graph << [
|
47
|
-
@graph << [
|
43
|
+
@graph << [a, RDF.type, EARL.Assertion]
|
44
|
+
@graph << [a, EARL.assertedBy, ODI]
|
45
|
+
@graph << [a, EARL.subject, CSVLINT]
|
46
|
+
@graph << [a, EARL.test, @test]
|
47
|
+
@graph << [a, EARL.mode, EARL.automatic]
|
48
48
|
r = RDF::Node.new
|
49
|
-
@graph << [
|
50
|
-
@graph << [
|
51
|
-
@graph << [
|
52
|
-
@graph << [
|
49
|
+
@graph << [a, EARL.result, r]
|
50
|
+
@graph << [r, RDF.type, EARL.TestResult]
|
51
|
+
@graph << [r, EARL.outcome, passed ? EARL.passed : EARL.failed]
|
52
|
+
@graph << [r, RDF::DC.date, DateTime.now]
|
53
53
|
end
|
54
54
|
|
55
55
|
def after_features(features)
|
56
|
-
RDF::Writer.for(:ttl).open("csvlint-earl.ttl", {
|
56
|
+
RDF::Writer.for(:ttl).open("csvlint-earl.ttl", {prefixes: {"earl" => EARL}, standard_prefixes: true, canonicalize: true, literal_shorthand: true}) do |writer|
|
57
57
|
writer << @graph
|
58
|
-
end
|
58
|
+
end
|
59
59
|
end
|
60
60
|
|
61
61
|
private
|
62
|
-
EARL = RDF::Vocabulary.new("http://www.w3.org/ns/earl#")
|
63
|
-
ODI = RDF::Resource.new("https://theodi.org/")
|
64
|
-
CSVLINT = RDF::Resource.new("https://github.com/theodi/csvlint.rb")
|
65
62
|
|
63
|
+
EARL = RDF::Vocabulary.new("http://www.w3.org/ns/earl#")
|
64
|
+
ODI = RDF::Resource.new("https://theodi.org/")
|
65
|
+
CSVLINT = RDF::Resource.new("https://github.com/theodi/csvlint.rb")
|
66
66
|
end
|
data/features/support/env.rb
CHANGED
@@ -1,23 +1,22 @@
|
|
1
|
-
require
|
2
|
-
Coveralls.wear_merged!(
|
1
|
+
require "coveralls"
|
2
|
+
Coveralls.wear_merged!("test_frameworks")
|
3
3
|
|
4
|
-
$:.unshift File.join(
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "..", "lib")
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
6
|
+
require "rspec/expectations"
|
7
|
+
require "cucumber/rspec/doubles"
|
8
|
+
require "csvlint"
|
9
|
+
require "byebug"
|
10
10
|
|
11
|
-
require
|
11
|
+
require "spork"
|
12
12
|
|
13
13
|
Spork.each_run do
|
14
|
-
require
|
14
|
+
require "csvlint"
|
15
15
|
end
|
16
16
|
|
17
17
|
class CustomWorld
|
18
18
|
def default_csv_options
|
19
|
-
|
20
|
-
}
|
19
|
+
{}
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
f.puts URI.open(uri,
|
27
|
-
|
28
|
-
|
29
|
-
|
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.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
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.
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
+
manifest = JSON.parse(URI.open("#{BASE_URI}manifest-validation.jsonld").read)
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
file.puts "Feature: #{manifest["label"]}"
|
66
|
+
file.puts ""
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
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
|
data/features/support/webmock.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
require
|
1
|
+
require "webmock/cucumber"
|
2
2
|
|
3
|
-
WebMock.disable_net_connect!(allow: %r{csvw/tests})
|
3
|
+
WebMock.disable_net_connect!(allow: %r{csvw/tests})
|