strong_csv 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c242c3b580ec02d431ae78bb7b8f370ef21248dae5fb89ea7ee158b0c9c04c6
4
- data.tar.gz: 2d880a823b44c1da78eeb6b79196c56ff46e937efe86ac17e8a522ea8b97f4a0
3
+ metadata.gz: 558e352db4ff9f2f719b9bb9f6217c007ff679133e2b02449858af679289281d
4
+ data.tar.gz: ff9881d662a6301fbdfe30bab16f02e440fc6939d0b4e89482216d1fe8902450
5
5
  SHA512:
6
- metadata.gz: 81bcf7718f3b792f6ecc394cccc5eb610b32ecb89b9dd492d3f39ab5dadebb633b1c9f4bd804de355902632fd1790fb3bb7b5789a7bb79a6eff6ee1860078491
7
- data.tar.gz: 39d417143c5262d1ac781bdd5414d5021e5018b5791a0d6ed6df7c0954b008154c8bc6542ce5cdc184b00b3c4c40a5bcfc4366872610a123feb3af93c991901d
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
- strong_json helps you to mitigate such a drudgery by letting you declare desired types beforehand.
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: ["`nil` can't be casted to Integer"] }
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
@@ -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[name] = [type, block]
29
+ @types << TypeWrapper.new(name: name, type: type, block: block, error_message: error_message)
30
30
  when ::String, ::Symbol
31
- @types[name.to_sym] = [type, block]
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.keys.all? { |k| k.is_a?(Integer) }
115
+ if @types.all? { |t| t.name.is_a?(Integer) }
116
116
  @headers = false
117
- elsif @types.keys.all? { |k| k.is_a?(Symbol) }
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.keys.inspect}"
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
@@ -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 |key, (type, block)|
25
- value_result = type.cast(row[key])
26
- @values[key] = block && value_result.success? ? block.call(value_result.value) : value_result.value
27
- @errors[key] = value_result.error_messages unless value_result.success?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StrongCSV
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
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: Hash[column, [declarable, ^(casted) -> untyped | nil]]
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: Hash[column, [declarable, (^(casted | ::Object | nil) -> untyped | nil)]], lineno: ::Integer) -> void
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.7.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: 2022-07-20 00:00:00.000000000 Z
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.3.7
62
+ rubygems_version: 3.4.6
62
63
  signing_key:
63
64
  specification_version: 4
64
65
  summary: Type check CSV objects