strong_csv 0.7.0 → 0.8.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/README.md +5 -4
- data/lib/strong_csv/let.rb +7 -7
- data/lib/strong_csv/row.rb +4 -5
- data/lib/strong_csv/type_wrapper.rb +30 -0
- data/lib/strong_csv/version.rb +1 -1
- data/lib/strong_csv.rb +1 -0
- data/sig/strong_csv.rbs +12 -4
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 558e352db4ff9f2f719b9bb9f6217c007ff679133e2b02449858af679289281d
|
4
|
+
data.tar.gz: ff9881d662a6301fbdfe30bab16f02e440fc6939d0b4e89482216d1fe8902450
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2dfc2c45143b4e4b435cb72627ce4a82311a5506e7fbe35449179684b2fb8ac048d509a23e1f8e08442c35d4188cdc5abc3cdd285165b1b8f1e69763b1993cf6
|
7
|
+
data.tar.gz: df17d984643c7830729a49c1284b60dafab1ddc14445d414d631bc87a135943cc9988cfb200f934c744d0b38cf8872a9d597ad422766cc89d08840c140f5dec4
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ Of course, it depends, but there would be common validation logic for CSV files.
|
|
14
14
|
For example, some columns may have to be integers because of database requirements.
|
15
15
|
It would be cumbersome to write such validations always.
|
16
16
|
|
17
|
-
|
17
|
+
strong_csv helps you to mitigate such a drudgery by letting you declare desired types beforehand.
|
18
18
|
|
19
19
|
## Installation
|
20
20
|
|
@@ -61,6 +61,7 @@ strong_csv = StrongCSV.new do
|
|
61
61
|
let :description, string?(within: 1..1000)
|
62
62
|
let :active, boolean
|
63
63
|
let :started_at, time?(format: "%Y-%m-%dT%H:%M:%S")
|
64
|
+
let :price, integer, error_message: "This should be Integer"
|
64
65
|
|
65
66
|
# Literal declaration
|
66
67
|
let :status, 0..6
|
@@ -91,8 +92,8 @@ strong_csv = StrongCSV.new do
|
|
91
92
|
end
|
92
93
|
|
93
94
|
data = <<~CSV
|
94
|
-
stock,tax_rate,name,active,status,priority,size,url
|
95
|
-
12,0.8,special item,True,4,20,M,https://example.com
|
95
|
+
stock,tax_rate,name,active,status,priority,size,url,price
|
96
|
+
12,0.8,special item,True,4,20,M,https://example.com,PRICE
|
96
97
|
CSV
|
97
98
|
|
98
99
|
strong_csv.parse(data, field_size_limit: 2048) do |row|
|
@@ -101,7 +102,7 @@ strong_csv.parse(data, field_size_limit: 2048) do |row|
|
|
101
102
|
row[:active] # => true
|
102
103
|
# do something with row
|
103
104
|
else
|
104
|
-
row.errors # => { user_id
|
105
|
+
row.errors # => {:price=>["This should be Integer"], :user_id=>["`nil` can't be casted to Integer"]}
|
105
106
|
# do something with row.errors
|
106
107
|
end
|
107
108
|
end
|
data/lib/strong_csv/let.rb
CHANGED
@@ -13,7 +13,7 @@ class StrongCSV
|
|
13
13
|
attr_reader :pickers
|
14
14
|
|
15
15
|
def initialize
|
16
|
-
@types =
|
16
|
+
@types = []
|
17
17
|
@headers = false
|
18
18
|
@pickers = {}
|
19
19
|
@picked = {}
|
@@ -22,13 +22,13 @@ class StrongCSV
|
|
22
22
|
# @param name [String, Symbol, Integer]
|
23
23
|
# @param type [StrongCSV::Type::Base]
|
24
24
|
# @param types [Array<StrongCSV::Type::Base>]
|
25
|
-
def let(name, type, *types, &block)
|
25
|
+
def let(name, type, *types, error_message: nil, &block)
|
26
26
|
type = types.empty? ? type : Types::Union.new(type, *types)
|
27
27
|
case name
|
28
28
|
when ::Integer
|
29
|
-
@types
|
29
|
+
@types << TypeWrapper.new(name: name, type: type, block: block, error_message: error_message)
|
30
30
|
when ::String, ::Symbol
|
31
|
-
@types
|
31
|
+
@types << TypeWrapper.new(name: name.to_sym, type: type, block: block, error_message: error_message)
|
32
32
|
else
|
33
33
|
raise TypeError, "Invalid type specified for `name`. `name` must be String, Symbol, or Integer: #{name.inspect}"
|
34
34
|
end
|
@@ -112,12 +112,12 @@ class StrongCSV
|
|
112
112
|
private
|
113
113
|
|
114
114
|
def validate_columns
|
115
|
-
if @types.
|
115
|
+
if @types.all? { |t| t.name.is_a?(Integer) }
|
116
116
|
@headers = false
|
117
|
-
elsif @types.
|
117
|
+
elsif @types.all? { |k| k.name.is_a?(Symbol) }
|
118
118
|
@headers = true
|
119
119
|
else
|
120
|
-
raise ArgumentError, "`types` cannot be mixed with Integer and Symbol keys: #{@types.
|
120
|
+
raise ArgumentError, "`types` cannot be mixed with Integer and Symbol keys: #{@types.map(&:name).inspect}"
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
data/lib/strong_csv/row.rb
CHANGED
@@ -4,7 +4,6 @@ class StrongCSV
|
|
4
4
|
# Row is a representation of a row in a CSV file, which has casted values with specified types.
|
5
5
|
class Row
|
6
6
|
extend Forwardable
|
7
|
-
using Types::Literal
|
8
7
|
|
9
8
|
def_delegators :@values, :[], :fetch, :slice
|
10
9
|
|
@@ -21,10 +20,10 @@ class StrongCSV
|
|
21
20
|
@values = {}
|
22
21
|
@errors = {}
|
23
22
|
@lineno = lineno
|
24
|
-
types.each do |
|
25
|
-
|
26
|
-
@values[
|
27
|
-
@errors[
|
23
|
+
types.each do |wrapper|
|
24
|
+
cell = row[wrapper.name]
|
25
|
+
@values[wrapper.name], error = wrapper.cast(cell)
|
26
|
+
@errors[wrapper.name] = error if error
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class StrongCSV # rubocop:disable Style/Documentation
|
4
|
+
using Types::Literal
|
5
|
+
|
6
|
+
# TypeWrapper holds the `type` along with `name` and `block`. It might be useful to store metadata for the type,
|
7
|
+
# such as `error_message`.
|
8
|
+
#
|
9
|
+
# @!attribute name
|
10
|
+
# @return [Symbol, Integer] The name for the type. This is the CSV header name. If the CSV does not have its header, Integer should be set.
|
11
|
+
# @!attribute type
|
12
|
+
# @return [StrongCSV::Type::Base]
|
13
|
+
# @!attribute error_message
|
14
|
+
# @return [String, nil] The error message returned if #cast fails. If omitted, the default error message will be used.
|
15
|
+
# @!attribute block
|
16
|
+
# @return [Proc]
|
17
|
+
TypeWrapper = Struct.new(:name, :type, :error_message, :block, keyword_init: true) do
|
18
|
+
def cast(value)
|
19
|
+
value_result = type.cast(value)
|
20
|
+
casted = block && value_result.success? ? block.call(value_result.value) : value_result.value
|
21
|
+
error = if value_result.success?
|
22
|
+
nil
|
23
|
+
else
|
24
|
+
error_message ? [error_message] : value_result.error_messages
|
25
|
+
end
|
26
|
+
|
27
|
+
[casted, error]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/strong_csv/version.rb
CHANGED
data/lib/strong_csv.rb
CHANGED
@@ -16,6 +16,7 @@ require_relative "strong_csv/types/optional"
|
|
16
16
|
require_relative "strong_csv/types/string"
|
17
17
|
require_relative "strong_csv/types/time"
|
18
18
|
require_relative "strong_csv/types/union"
|
19
|
+
require_relative "strong_csv/type_wrapper"
|
19
20
|
require_relative "strong_csv/let"
|
20
21
|
require_relative "strong_csv/row"
|
21
22
|
|
data/sig/strong_csv.rbs
CHANGED
@@ -107,14 +107,13 @@ class StrongCSV
|
|
107
107
|
class Let
|
108
108
|
@picked: Hash[untyped, untyped]
|
109
109
|
|
110
|
-
attr_reader types:
|
110
|
+
attr_reader types: Array[TypeWrapper]
|
111
111
|
attr_reader headers: bool
|
112
112
|
attr_reader pickers: Hash[untyped, Proc]
|
113
113
|
|
114
114
|
def initialize: -> void
|
115
115
|
|
116
|
-
def let: (::String | column, declarable, *declarable) -> void
|
117
|
-
| (::String | column, declarable, *declarable) { (casted) -> untyped } -> void
|
116
|
+
def let: (::String | column, declarable, *declarable, ?error_message: ::String?) ?{ (casted) -> untyped } -> void
|
118
117
|
|
119
118
|
def pick: (column, as: ::Symbol) { (Array[::String]) -> untyped } -> void
|
120
119
|
|
@@ -153,11 +152,20 @@ class StrongCSV
|
|
153
152
|
attr_reader errors: Hash[column, Array[::String]]
|
154
153
|
attr_reader lineno: ::Integer
|
155
154
|
|
156
|
-
def initialize: (row: Array[::String] | CSV::Row, types:
|
155
|
+
def initialize: (row: Array[::String] | CSV::Row, types: Array[TypeWrapper], lineno: ::Integer) -> void
|
157
156
|
|
158
157
|
def valid?: -> bool
|
159
158
|
end
|
160
159
|
|
160
|
+
class TypeWrapper
|
161
|
+
attr_accessor name: (::Symbol | ::Integer)
|
162
|
+
attr_accessor type: declarable
|
163
|
+
attr_accessor error_message: (::String | nil)
|
164
|
+
attr_accessor block: (^(casted | ::Object | nil) -> untyped | nil)
|
165
|
+
|
166
|
+
def cast: (::String | nil) -> [(casted | ::Object | nil), (::String | nil)]
|
167
|
+
end
|
168
|
+
|
161
169
|
class Error < StandardError
|
162
170
|
end
|
163
171
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_csv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yutaka Kamei
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: strong_csv is a type checker for a CSV file. It lets developers declare
|
14
14
|
types for each column to ensure all cells are satisfied with desired types.
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- lib/strong_csv.rb
|
24
24
|
- lib/strong_csv/let.rb
|
25
25
|
- lib/strong_csv/row.rb
|
26
|
+
- lib/strong_csv/type_wrapper.rb
|
26
27
|
- lib/strong_csv/types/base.rb
|
27
28
|
- lib/strong_csv/types/boolean.rb
|
28
29
|
- lib/strong_csv/types/float.rb
|
@@ -58,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
59
|
- !ruby/object:Gem::Version
|
59
60
|
version: '0'
|
60
61
|
requirements: []
|
61
|
-
rubygems_version: 3.
|
62
|
+
rubygems_version: 3.4.6
|
62
63
|
signing_key:
|
63
64
|
specification_version: 4
|
64
65
|
summary: Type check CSV objects
|