csv-probe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 138cd78aa31ccab66bbaa4a629c4f2c0d3b37a547006ed635e6fed935a223051
4
+ data.tar.gz: 87f700fcb9c7a620f101b5a63bf095b9f6a611f72d83f876b11ca33797564a72
5
+ SHA512:
6
+ metadata.gz: 1357f59d7446f937c3719feeafd86aa29a10844203d72dd20f30c338243ad347adeca2c5d1dca93195cd223403fb024263330f398f029455f8ae5562fd906812
7
+ data.tar.gz: aee4fa829429c8cc4590351da2b303b5879a163b412d987ec7dff63f5c45e5e8e9e4b5dd40af4619a19ceaa166bf09670e3f4f0b50b79f83e4c048552ec0ec7c
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-01-02
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in csv-probe.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.21"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 homebase.dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Probe
2
+
3
+ Probe provides a simple framework for CSV::Table and CSV::Row linting using custom rules.
4
+ Probe extens CSV::Table and CSV::Row with a `lint(rules, opts)` method.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'csv-probe'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install csv-probe
21
+
22
+ ## Usage
23
+
24
+ Example how to use Probe
25
+
26
+ ```
27
+ # load CSV into CSV::Table
28
+ csv_table = CSV.parse(<<~ROWS, headers: true)
29
+ col1,col2,col3,col4,col5,col6,col7
30
+ 0,NU,customer,1,02-12-2021,FO;FO;RU,eagle|hawk
31
+ 0,PA,customer,2,03-12-2021,SHA;AN;RU,badger|ant
32
+ 0,TE,guest,1000,06-11-2021,RU,spider|racoon|ant
33
+ ROWS
34
+ checks = [Probe::ColumnIsEqualTo.new("col1", "0"),
35
+ Probe::ColumnMatchesRegEx.new("col2", /^(NU|PA|TE)$/),
36
+ Probe::ColumnIsOneOf.new("col3", ["customer","guest"]),
37
+ Probe::ColumnMeetsCondition.new("col4", ->(val, _opts) { Integer(val, exception: false) != nil }, "Not an Integer"),
38
+ Probe::ColumnIsDate.new("col5", "%d-%m-%Y"),
39
+ Probe::ColumnIsListWithDomain.new("col6", ["FO", "RU", "SHA", "AN"], ";"),
40
+ Probe::ColumnIsSetWithDomain.new("col7", ["eagle", "hawk", "badger", "spider", "racoon", "ant"], "|")]
41
+
42
+ # lint table
43
+ csv_table.lint(checks)
44
+
45
+ # lint single row of table
46
+ csv_table[0].lint(checks)
47
+ ```
48
+
49
+ ## Development
50
+
51
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
52
+
53
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
54
+
55
+ ## Contributing
56
+
57
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/csv-probe.
58
+
59
+ ## License
60
+
61
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "csv/probe"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/csv-probe.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/csv/probe/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "csv-probe"
7
+ spec.version = Probe::VERSION
8
+ spec.authors = ["homebase.dev"]
9
+ spec.email = ["homebase.dev@gmail.com"]
10
+
11
+ spec.summary = "Allows validation of CSV::Table or CSV::Rows via custom rules"
12
+ spec.description = "This gem provides a simple framework for CSV::Table/CSV::Row validation using custom rules for columns and rows"
13
+ spec.homepage = "https://gitlab.com/homebase-dev/csv-probe"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://gitlab.com/homebase-dev/csv-probe"
21
+ spec.metadata["changelog_uri"] = "https://gitlab.com/homebase-dev/csv-probe"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ spec.add_dependency "csv", "~> 3.1.9"
36
+ spec.add_dependency "terminal-table", "~> 3.0"
37
+
38
+ # For more information and examples about making a new gem, checkout our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Probe
4
+ class Error < StandardError; end
5
+
6
+ class LintingError < Error; end
7
+
8
+ class RowError < LintingError
9
+ def initialize(msg = "CheckRowError")
10
+ super
11
+ end
12
+ end
13
+
14
+ class RowWarning < LintingError
15
+ def initialize(msg = "CheckRowWarning")
16
+ super
17
+ end
18
+ end
19
+
20
+ class ColumnError < LintingError
21
+ def initialize(msg = "CheckColumnError")
22
+ super
23
+ end
24
+ end
25
+
26
+ class ColumnWarning < LintingError
27
+ def initialize(msg = "CheckColumnWarning")
28
+ super
29
+ end
30
+ end
31
+
32
+ class RowMeetsCondition
33
+ attr_accessor :ok_condition_fn, :fail_msg, :severity, :pre_checks
34
+
35
+ def initialize(ok_condition_fn, fail_msg)
36
+ @pre_checks = []
37
+ @ok_condition_fn = ok_condition_fn
38
+ @fail_msg = fail_msg
39
+ @severity = "ERROR"
40
+ @error_classes = {
41
+ "ERROR" => RowError,
42
+ "WARNING" => RowWarning
43
+ }
44
+ end
45
+
46
+ def evaluate_pre_checks(row, opts)
47
+ @pre_checks.each do |c|
48
+ c.evaluate(row, opts)
49
+ end
50
+ end
51
+
52
+ def evaluate(row, opts = {})
53
+ evaluate_pre_checks(row, opts)
54
+ raise @error_classes.fetch(@severity), error_msg(row, opts) unless @ok_condition_fn.call(row, opts)
55
+ end
56
+
57
+ def error_msg(row, opts)
58
+ row_location = source_row_location(row, opts)
59
+ row_str = source_row(row, opts) # [#{self.class}] verbose only?
60
+ err_msg = "#{row_location} [#{@severity}] #{render_pretty_fail_msg(row, opts)}\n"
61
+ if opts[:verbose]
62
+ err_msg = "#{err_msg}#{self.class.name.split("::").last}\n"
63
+ err_msg = "#{err_msg}#{@error_classes.fetch(@severity).name.split("::").last}\n"
64
+ end
65
+ "#{err_msg}#{row_str}\n"
66
+ end
67
+
68
+ def render_fail_msg(row, opts)
69
+ return @fail_msg.call(row, opts) if @fail_msg.is_a?(Proc)
70
+
71
+ @fail_msg
72
+ end
73
+
74
+ def render_pretty_fail_msg(row, opts)
75
+ render_fail_msg(row, opts)
76
+ end
77
+
78
+ def source_row_location(_row, opts)
79
+ "#{opts[:fpath]}:#{opts[:lineno]}"
80
+ end
81
+
82
+ def source_row(row, opts = {})
83
+ return report_columns_hash(row, opts) if opts[:only_report_columns]
84
+
85
+ if opts[:source_row_renderer]
86
+ return row.inspect if opts[:source_row_renderer] == "inspect"
87
+ return "#{Terminal::Table.new rows: row.to_a}\n" if opts[:source_row_renderer] == "tabular"
88
+ return "#{Terminal::Table.new rows: row.to_a.transpose}\n" if opts[:source_row_renderer] == "tabular_transposed"
89
+ end
90
+ row
91
+ end
92
+
93
+ # def row_colval(row, varname, opts)
94
+ # return row[varname] if varname.is_a? Integer
95
+ # return row.fetch(varname)
96
+ # end
97
+
98
+ def report_columns_hash(row, opts)
99
+ h = {}
100
+ opts[:only_report_columns].each { |varname| h[varname] = row.fetch(varname) }
101
+ h
102
+ end
103
+ end
104
+
105
+ class ColumnMeetsCondition < RowMeetsCondition
106
+ attr_accessor :varname, :ok_condition_fn, :fail_msg
107
+
108
+ def initialize(varname, ok_condition_fn, fail_msg)
109
+ super(ok_condition_fn, fail_msg)
110
+ @varname = varname
111
+ @error_classes = {
112
+ "ERROR" => ColumnError,
113
+ "WARNING" => ColumnWarning
114
+ }
115
+ end
116
+
117
+ def render_pretty_fail_msg(row, opts)
118
+ "Unexpected value:#{row.fetch(@varname).inspect} for column:#{@varname.inspect}, #{render_fail_msg(row, opts)}"
119
+ end
120
+
121
+ def evaluate(row, opts = {})
122
+ evaluate_pre_checks(row, opts)
123
+ raise @error_classes.fetch(@severity), error_msg(row, opts) unless @ok_condition_fn.call(row.fetch(@varname), opts)
124
+ end
125
+ end
126
+
127
+ class ColumnIsEqualTo < ColumnMeetsCondition
128
+ def initialize(varname, expected_val, _placeholder = nil)
129
+ super(varname, nil, nil)
130
+ @ok_condition_fn = ->(val, _cfg) { val == expected_val }
131
+ @fail_msg = "expected to be #{expected_val.inspect}"
132
+ end
133
+ end
134
+
135
+ class ColumnIsOneOf < ColumnMeetsCondition
136
+ def initialize(varname, expected_vals_arr, _placeholder = nil)
137
+ super(varname, nil, nil)
138
+ @ok_condition_fn = ->(val, _cfg) { expected_vals_arr.include?(val) }
139
+ @fail_msg = "expected to be one of #{expected_vals_arr.inspect}"
140
+ end
141
+ end
142
+
143
+ class ColumnIsDate < ColumnMeetsCondition
144
+ def initialize(varname, expected_date_format, _placeholder = nil)
145
+ super(varname, nil, nil)
146
+ @ok_condition_fn = lambda { |val, _cfg|
147
+ success = true
148
+ begin
149
+ Date.strptime(val, expected_date_format)
150
+ rescue Date::Error
151
+ success = false
152
+ end
153
+ return success
154
+ }
155
+ @fail_msg = "expected date with format #{expected_date_format.inspect}"
156
+ end
157
+ end
158
+
159
+ # class ColumnIsInteger < ColumnMeetsCondition
160
+ # def initialize(varname, placeholder1=nil, placeholder=nil)
161
+ # super(varname, nil, nil)
162
+ # @ok_condition_fn = ->(val, cfg){ val.is_a? Integer } # TODO: achtung 12abc -> integer auch 1.1 wird zu 1... ist nicht so einfach, lieber über regex prüfen
163
+ # @fail_msg = "expected to be an Integer"
164
+ # end
165
+ # end
166
+
167
+ class ColumnMatchesRegEx < ColumnMeetsCondition
168
+ def initialize(varname, expected_regex_pattern, _placeholder = nil)
169
+ super(varname, nil, nil)
170
+ @ok_condition_fn = ->(val, _cfg) { val.to_s =~ expected_regex_pattern }
171
+ @fail_msg = "expected to match regex pattern #{expected_regex_pattern.inspect}"
172
+ end
173
+ end
174
+
175
+ # TODO: fixme uniq als eigenen check machen
176
+ class ColumnIsListWithDomain < ColumnMeetsCondition
177
+ def initialize(varname, expected_items_arr, separator, _placeholder = nil)
178
+ super(varname, nil, nil)
179
+ @ok_condition_fn = lambda { |val, _cfg|
180
+ items = val.split(separator)
181
+ return items.all? { |e| expected_items_arr.include?(e) }
182
+ }
183
+ @fail_msg = lambda { |row, _opts|
184
+ items = row.fetch(@varname).split(separator)
185
+ diff_items = items - expected_items_arr
186
+ "expected that tokenized items of value #{items.inspect} are a subset of #{expected_items_arr.inspect}, but items #{diff_items.inspect} are not"
187
+ }
188
+ end
189
+ end
190
+
191
+ class ColumnIsSet < ColumnMeetsCondition
192
+ def initialize(varname, separator, _placeholder = nil)
193
+ super(varname, nil, nil)
194
+ @ok_condition_fn = lambda { |val, _cfg|
195
+ return true if val.to_s == ""
196
+
197
+ items = val.split(separator)
198
+ all_uniq = (items.size == items.uniq.size)
199
+ return all_uniq
200
+ }
201
+ @fail_msg = lambda { |row, _opts|
202
+ items = row.fetch(@varname).split(separator)
203
+ non_uniq_items = items.detect { |e| items.count(e) > 1 }
204
+ "expected that items of tokenized value #{items.inspect} are uniqe, but items #{non_uniq_items.inspect} are not"
205
+ }
206
+ end
207
+ end
208
+
209
+ class ColumnIsSetWithDomain < ColumnMeetsCondition
210
+ def initialize(varname, expected_items_arr, separator, _placeholder = nil)
211
+ super(varname, nil, nil)
212
+ @pre_checks << ColumnIsSet.new(varname, separator)
213
+ @ok_condition_fn = lambda { |val, _cfg|
214
+ return true if val.to_s == ""
215
+
216
+ items = val.split(separator)
217
+ all_valid = items.all? { |i| expected_items_arr.include?(i) }
218
+ return all_valid
219
+ }
220
+ @fail_msg = lambda { |row, _opts|
221
+ items = row.fetch(@varname).split(separator)
222
+ "expected that items of tokenized value #{items.inspect} are a subset of #{expected_items_arr.inspect}"
223
+ }
224
+ # @fail_msg = ->(row, opts) { "Unexpected value:#{row.fetch(@varname).inspect} for column:#{@varname.inspect}, expected that items of tokenized value #{row.fetch(@varname).split(separator)} are uniqe and a subset of #{expected_items_arr.inspect}" }
225
+ end
226
+ end
227
+
228
+ # TODO: nice to have
229
+ # * if_second_not_empty_then_first_yes_check(varname1, varname2)
230
+ # * if_first_eyes_then_second_not_empty_check(varname1, varname2)
231
+ # * if_first_yes_then_others_too_checks(varname1, varname_arr)
232
+ # * if_first_NC2_then_others_not_yes_checks(varname1, varname_arr)
233
+ # * if_first_yes_then_also_second_check(varname1, varname2)
234
+ # * warn_if_first_yes_then_others_not_empty_checks(main_varname, sub_varnames_arr)
235
+ # * is_comment_of_size(main_varname, char_len)
236
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Probe
4
+ VERSION = "0.1.0"
5
+ end
data/lib/csv/probe.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+ require "terminal-table"
5
+ require_relative "probe/version"
6
+ require_relative "probe/checks"
7
+
8
+ # Probe provides methods for linting a:
9
+ # * CSV::Table
10
+ # * CSV::Row
11
+ #
12
+ # The linting methods:
13
+ # * lint_rows takes following arguments
14
+ # * csv_rows: CSV::Table
15
+ # * checks: Array of Probe Checks and
16
+ # * options
17
+ # * lint_row takes following arguments
18
+ # * csv_row: CSV::Row
19
+ # * checks: Array of Probe Checks and
20
+ # * optsions
21
+ module Probe
22
+ def self.lint_rows(csv_rows, checks, opts = {})
23
+ lineno_start = opts[:headers] ? 2 : 1
24
+ csv_rows.each.with_index(lineno_start) do |row, lineno|
25
+ opts[:lineno] = lineno
26
+ lint_row(row, checks, opts)
27
+ end
28
+ end
29
+
30
+ def self.lint_row(csv_row, checks, opts = {})
31
+ unless csv_row.is_a? CSV::Row
32
+ raise Error "lint_row(csv_row,...), csv_row is not of type CSV::Row, but of type '#{csv_row.class}'"
33
+ end
34
+
35
+ checks.each do |check|
36
+ check.evaluate(csv_row, opts)
37
+ rescue LintingError => e
38
+ raise e unless opts[:exception] == false
39
+
40
+ puts e.message # if exception oppressed, write error out on stdout
41
+ end
42
+ end
43
+ end
44
+
45
+ # Extend CSV::Table with .lint(...) method
46
+ class CSV::Table
47
+ def lint(checks, opts = {})
48
+ opts[:headers] = true unless opts.key?(:headers) # CSV::Table has always headers, hence set :headers = true
49
+ Probe.lint_rows(self, checks, opts)
50
+ end
51
+ end
52
+
53
+ # Extend CSV::Row with .lint(...) method
54
+ class CSV::Row
55
+ def lint(checks, opts = {})
56
+ Probe.lint_row(self, checks, opts)
57
+ end
58
+ end
data/sig/csv/probe.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Csv
2
+ module Probe
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv-probe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - homebase.dev
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: csv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: terminal-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: This gem provides a simple framework for CSV::Table/CSV::Row validation
42
+ using custom rules for columns and rows
43
+ email:
44
+ - homebase.dev@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".rspec"
50
+ - ".rubocop.yml"
51
+ - CHANGELOG.md
52
+ - Gemfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - bin/console
57
+ - bin/setup
58
+ - csv-probe.gemspec
59
+ - lib/csv/probe.rb
60
+ - lib/csv/probe/checks.rb
61
+ - lib/csv/probe/version.rb
62
+ - sig/csv/probe.rbs
63
+ homepage: https://gitlab.com/homebase-dev/csv-probe
64
+ licenses:
65
+ - MIT
66
+ metadata:
67
+ homepage_uri: https://gitlab.com/homebase-dev/csv-probe
68
+ source_code_uri: https://gitlab.com/homebase-dev/csv-probe
69
+ changelog_uri: https://gitlab.com/homebase-dev/csv-probe
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 2.6.0
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubygems_version: 3.2.29
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Allows validation of CSV::Table or CSV::Rows via custom rules
89
+ test_files: []