csvlint 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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})
|