strong_csv 0.4.0 → 0.7.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: 8078bf75d84307eb32f647a418d2cd2214d6891a78a25a73f7589b82fec0716c
4
- data.tar.gz: 318af2a0c3a42ac7141819b06e729a5e98967c5106988bc4349b308f9c9d87f8
3
+ metadata.gz: 1c242c3b580ec02d431ae78bb7b8f370ef21248dae5fb89ea7ee158b0c9c04c6
4
+ data.tar.gz: 2d880a823b44c1da78eeb6b79196c56ff46e937efe86ac17e8a522ea8b97f4a0
5
5
  SHA512:
6
- metadata.gz: 63aa30f83182e0cab08f816124945d9da91149faaa82017ba0a80ca8b39ef57fed5eab521db1efd8404045aadba189550bd779f195434cef2294975dc30216f3
7
- data.tar.gz: e4e74ea2446efcf32f6e67fe8be5df207ae8dbd66b392df4c2fb8895cd71aebf70894d062e64eee2814295aa00f29876afcb95b4c3a88ffffd7be0c8e9a9ba21
6
+ metadata.gz: 81bcf7718f3b792f6ecc394cccc5eb610b32ecb89b9dd492d3f39ab5dadebb633b1c9f4bd804de355902632fd1790fb3bb7b5789a7bb79a6eff6ee1860078491
7
+ data.tar.gz: 39d417143c5262d1ac781bdd5414d5021e5018b5791a0d6ed6df7c0954b008154c8bc6542ce5cdc184b00b3c4c40a5bcfc4366872610a123feb3af93c991901d
data/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  <a href="https://rubygems.org/gems/strong_csv"><img alt="strong_csv" src="https://img.shields.io/gem/v/strong_csv"></a>
4
4
 
5
- NOTE: This repository is still under development 🚧🚜🚧
6
-
7
5
  Type checker for a CSV file inspired by [strong_json](https://github.com/soutaro/strong_json).
8
6
 
9
7
  ## Motivation
@@ -81,8 +79,6 @@ strong_csv = StrongCSV.new do
81
79
  # Regular expressions
82
80
  let :url, %r{\Ahttps://}
83
81
 
84
- # TODO: The followings are not implemented so far.
85
-
86
82
  # Custom validation
87
83
  #
88
84
  # This example sees the database to fetch exactly stored `User` IDs,
@@ -105,12 +101,30 @@ strong_csv.parse(data, field_size_limit: 2048) do |row|
105
101
  row[:active] # => true
106
102
  # do something with row
107
103
  else
108
- row.errors # => { user_id: ["must be present", "must be an integer"] }
104
+ row.errors # => { user_id: ["`nil` can't be casted to Integer"] }
109
105
  # do something with row.errors
110
106
  end
111
107
  end
112
108
  ```
113
109
 
110
+ You can also define types without CSV headers by specifying column numbers.
111
+ Note the numbers must start from `0` (zero-based index).
112
+
113
+ ```ruby
114
+ StrongCSV.new do
115
+ let 0, integer
116
+ let 1, string
117
+ let 2, 1..10
118
+ end
119
+ ```
120
+
121
+ This declaration expects a CSV has the contents like this:
122
+
123
+ ```csv
124
+ 123,abc,3
125
+ 830,mno,10
126
+ ```
127
+
114
128
  ## Available types
115
129
 
116
130
  <table>
@@ -171,7 +185,7 @@ end
171
185
  ### `integer` and `integer?`
172
186
 
173
187
  The value must be casted to Integer. `integer?` allows the value to be `nil`, so you can declare optional integer type
174
- for columns.
188
+ for columns. It also lets you allow values that satisfy the specified limitation through `:constraint`.
175
189
 
176
190
  _Example_
177
191
 
@@ -179,25 +193,30 @@ _Example_
179
193
  strong_csv = StrongCSV.new do
180
194
  let :stock, integer
181
195
  let :state, integer?
196
+ let :user_id, integer(constraint: ->(v) { user_ids.include?(v)})
197
+ pick :user_id, as: :user_ids do |values|
198
+ User.where(id: values).ids
199
+ end
182
200
  end
183
201
 
184
202
  result = strong_csv.parse(<<~CSV)
185
- stock,state
186
- 12,0
187
- 20,
188
- non-integer,1
203
+ stock,state,user_id
204
+ 12,0,1
205
+ 20,,2
206
+ non-integer,1,4
189
207
  CSV
190
208
 
191
209
  result.map(&:valid?) # => [true, true, false]
192
- result[0].slice(:stock, :state) # => {:stock=>12, :state=>0}
193
- result[1].slice(:stock, :state) # => {:stock=>20, :state=>nil}
194
- result[2].slice(:stock, :state) # => {:stock=>"non-integer", :state=>1} ("non-integer" cannot be casted to Integer)
210
+ result[0].slice(:stock, :state, :user_id) # => {:stock=>12, :state=>0, :user_id=>1}
211
+ result[1].slice(:stock, :state, :user_id) # => {:stock=>20, :state=>nil, :user_id=>2}
212
+ result[2].slice(:stock, :state, :user_id) # => {:stock=>"non-integer", :state=>1, :user_id=>"4"}
213
+ result[2].errors.slice(:stock, :user_id) # => {:stock=>["`\"non-integer\"` can't be casted to Integer"], :user_id=>["`\"4\"` does not satisfy the specified constraint"]}
195
214
  ```
196
215
 
197
216
  ### `float` and `float?`
198
217
 
199
218
  The value must be casted to Float. `float?` allows the value to be `nil`, so you can declare optional float type for
200
- columns.
219
+ columns. It also lets you allow values that satisfy the specified limitation through `:constraint`.
201
220
 
202
221
  _Example_
203
222
 
@@ -383,43 +402,6 @@ result[1].slice(:priority, :size) # => {:priority=>30, :size=>"A"} ("A" is not o
383
402
  result[2].slice(:priority, :size) # => {:priority=>"11", :size=>"S"} (11 is not one of 10, 20, and 30)
384
403
  ```
385
404
 
386
- ## I18n (Internationalization)
387
-
388
- strong_csv depends on [i18n](https://rubygems.org/gems/i18n) for internationalization.
389
- If you want to have a locale-specific error message, put the message catalog in your locale files.
390
- Here is an example of a locale file.
391
-
392
- ```yaml
393
- ja:
394
- strong_csv:
395
- boolean:
396
- cant_be_casted: "`%{value}`はBooleanに変換できません"
397
- float:
398
- cant_be_casted: "`%{value}`はFloatに変換できません"
399
- integer:
400
- cant_be_casted: "`%{value}`はIntegerに変換できません"
401
- literal:
402
- integer:
403
- unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
404
- cant_be_casted: "`%{expected}`ではなく`%{value}`が入力されています"
405
- float:
406
- unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
407
- cant_be_casted: "`%{value}`はFloatに変換できません"
408
- string:
409
- unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
410
- range:
411
- cant_be_casted: "`%{value}`は`%{expected}`の始端に変換できません"
412
- out_of_range: "`%{value}`は`%{range}`の範囲外です"
413
- regexp:
414
- cant_be_casted: "`%{value}`はStringに変換できません"
415
- unexpected: "`%{value}`は`%{expected}`とマッチしませんでした"
416
- string:
417
- cant_be_casted: "`%{value}`はStringに変換できません"
418
- out_of_range: "`%{value}`の文字数は`%{range}`の範囲外です"
419
- time:
420
- cant_be_casted: "`%{value}`は`%{time_format}`でTimeに変換できません"
421
- ```
422
-
423
405
  ## Contributing
424
406
 
425
407
  Bug reports and pull requests are welcome on the [GitHub repository](https://github.com/yykamei/strong_csv).
@@ -9,9 +9,14 @@ class StrongCSV
9
9
  # @return [Boolean]
10
10
  attr_reader :headers
11
11
 
12
+ # @return [Hash]
13
+ attr_reader :pickers
14
+
12
15
  def initialize
13
16
  @types = {}
14
17
  @headers = false
18
+ @pickers = {}
19
+ @picked = {}
15
20
  end
16
21
 
17
22
  # @param name [String, Symbol, Integer]
@@ -30,12 +35,35 @@ class StrongCSV
30
35
  validate_columns
31
36
  end
32
37
 
33
- def integer
34
- Types::Integer.new
38
+ # #pick is intended for defining a singleton method with `:as`.
39
+ # This might be useful for the case where you want to receive IDs that are stored in a database,
40
+ # and you want to make sure the IDs actually exist.
41
+ #
42
+ # @example
43
+ # pick :user_id, as: :user_ids do |user_ids|
44
+ # User.where(id: user_ids).ids
45
+ # end
46
+ #
47
+ # @param column [Symbol, Integer]
48
+ # @param as [Symbol]
49
+ # @yieldparam values [Array<String>] The values for the column. NOTE: This is an array of String, not casted values.
50
+ def pick(column, as:, &block)
51
+ define_singleton_method(as) do
52
+ @picked[as]
53
+ end
54
+ @pickers[as] = lambda do |csv|
55
+ @picked[as] = block.call(csv.map { |row| row[column] })
56
+ end
35
57
  end
36
58
 
37
- def integer?
38
- optional(integer)
59
+ # @param options [Hash] See `Types::Integer#initialize` for more details.
60
+ def integer(**options)
61
+ Types::Integer.new(**options)
62
+ end
63
+
64
+ # @param options [Hash] See `Types::Integer#initialize` for more details.
65
+ def integer?(**options)
66
+ optional(integer(**options))
39
67
  end
40
68
 
41
69
  def boolean
@@ -46,12 +74,14 @@ class StrongCSV
46
74
  optional(boolean)
47
75
  end
48
76
 
49
- def float
50
- Types::Float.new
77
+ # @param options [Hash] See `Types::Float#initialize` for more details.
78
+ def float(**options)
79
+ Types::Float.new(**options)
51
80
  end
52
81
 
53
- def float?
54
- optional(float)
82
+ # @param options [Hash] See `Types::Float#initialize` for more details.
83
+ def float?(**options)
84
+ optional(float(**options))
55
85
  end
56
86
 
57
87
  # @param options [Hash] See `Types::String#initialize` for more details.
@@ -59,6 +89,7 @@ class StrongCSV
59
89
  Types::String.new(**options)
60
90
  end
61
91
 
92
+ # @param options [Hash] See `Types::String#initialize` for more details.
62
93
  def string?(**options)
63
94
  optional(string(**options))
64
95
  end
@@ -68,6 +99,7 @@ class StrongCSV
68
99
  Types::Time.new(**options)
69
100
  end
70
101
 
102
+ # @param options [Hash] See `Types::Time#initialize` for more details.
71
103
  def time?(**options)
72
104
  optional(time(**options))
73
105
  end
@@ -28,6 +28,9 @@ class StrongCSV
28
28
  end
29
29
  end
30
30
 
31
+ # It returns true if the row has no errors.
32
+ #
33
+ # @return [Boolean]
31
34
  def valid?
32
35
  @errors.empty?
33
36
  end
@@ -17,7 +17,7 @@ class StrongCSV
17
17
  boolean = FALSE_VALUES.include?(value) ? false : nil
18
18
  return ValueResult.new(value: boolean, original_value: value) unless boolean.nil?
19
19
 
20
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.boolean.cant_be_casted", value: value.inspect, default: :"_strong_csv.boolean.cant_be_casted")])
20
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Boolean"])
21
21
  end
22
22
  end
23
23
  end
@@ -4,13 +4,27 @@ class StrongCSV
4
4
  module Types
5
5
  # Float type
6
6
  class Float < Base
7
+ DEFAULT_CONSTRAINT = ->(_) { true }
8
+ private_constant :DEFAULT_CONSTRAINT
9
+
10
+ # @param constraint [Proc]
11
+ def initialize(constraint: DEFAULT_CONSTRAINT)
12
+ super()
13
+ @constraint = constraint
14
+ end
15
+
7
16
  # @todo Use :exception for Float after we drop the support of Ruby 2.5
8
17
  # @param value [Object] Value to be casted to Float
9
18
  # @return [ValueResult]
10
19
  def cast(value)
11
- ValueResult.new(value: Float(value), original_value: value)
20
+ float = Float(value)
21
+ if @constraint.call(float)
22
+ ValueResult.new(value: float, original_value: value)
23
+ else
24
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` does not satisfy the specified constraint"])
25
+ end
12
26
  rescue ArgumentError, TypeError
13
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.float.cant_be_casted", value: value.inspect, default: :"_strong_csv.float.cant_be_casted")])
27
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Float"])
14
28
  end
15
29
  end
16
30
  end
@@ -4,13 +4,27 @@ class StrongCSV
4
4
  module Types
5
5
  # Integer type
6
6
  class Integer < Base
7
+ DEFAULT_CONSTRAINT = ->(_) { true }
8
+ private_constant :DEFAULT_CONSTRAINT
9
+
10
+ # @param constraint [Proc]
11
+ def initialize(constraint: DEFAULT_CONSTRAINT)
12
+ super()
13
+ @constraint = constraint
14
+ end
15
+
7
16
  # @todo Use :exception for Integer after we drop the support of Ruby 2.5
8
17
  # @param value [Object] Value to be casted to Integer
9
18
  # @return [ValueResult]
10
19
  def cast(value)
11
- ValueResult.new(value: Integer(value), original_value: value)
20
+ int = Integer(value)
21
+ if @constraint.call(int)
22
+ ValueResult.new(value: int, original_value: value)
23
+ else
24
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` does not satisfy the specified constraint"])
25
+ end
12
26
  rescue ArgumentError, TypeError
13
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.integer.cant_be_casted", value: value.inspect, default: :"_strong_csv.integer.cant_be_casted")])
27
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Integer"])
14
28
  end
15
29
  end
16
30
  end
@@ -13,10 +13,10 @@ class StrongCSV
13
13
  if int == self
14
14
  ValueResult.new(value: int, original_value: value)
15
15
  else
16
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.integer.unexpected", value: int.inspect, expected: inspect, default: :"_strong_csv.literal.integer.unexpected")])
16
+ ValueResult.new(original_value: value, error_messages: ["`#{inspect}` is expected, but `#{int.inspect}` was given"])
17
17
  end
18
18
  rescue ArgumentError, TypeError
19
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.integer.cant_be_casted", value: value.inspect, default: :"_strong_csv.literal.integer.cant_be_casted")])
19
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Integer"])
20
20
  end
21
21
  end
22
22
 
@@ -28,10 +28,10 @@ class StrongCSV
28
28
  if float == self
29
29
  ValueResult.new(value: float, original_value: value)
30
30
  else
31
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.float.unexpected", value: float.inspect, expected: inspect, default: :"_strong_csv.literal.float.unexpected")])
31
+ ValueResult.new(original_value: value, error_messages: ["`#{inspect}` is expected, but `#{float.inspect}` was given"])
32
32
  end
33
33
  rescue ArgumentError, TypeError
34
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.float.cant_be_casted", value: value.inspect, default: :"_strong_csv.literal.float.cant_be_casted")])
34
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Float"])
35
35
  end
36
36
  end
37
37
 
@@ -39,7 +39,7 @@ class StrongCSV
39
39
  # @param value [Object] Value to be casted to Range
40
40
  # @return [ValueResult]
41
41
  def cast(value)
42
- return ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.range.cant_be_casted", value: value.inspect, expected: inspect, default: :"_strong_csv.literal.range.cant_be_casted")]) if value.nil?
42
+ return ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to the beginning of `#{inspect}`"]) if value.nil?
43
43
 
44
44
  casted = case self.begin
45
45
  when ::Float
@@ -54,10 +54,10 @@ class StrongCSV
54
54
  if cover?(casted)
55
55
  ValueResult.new(value: casted, original_value: value)
56
56
  else
57
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.range.out_of_range", value: casted.inspect, range: inspect, default: :"_strong_csv.literal.range.out_of_range")])
57
+ ValueResult.new(original_value: value, error_messages: ["`#{casted.inspect}` is not within `#{inspect}`"])
58
58
  end
59
59
  rescue ArgumentError
60
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.range.cant_be_casted", value: value.inspect, expected: inspect, default: :"_strong_csv.literal.range.cant_be_casted")])
60
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to the beginning of `#{inspect}`"])
61
61
  end
62
62
  end
63
63
 
@@ -68,7 +68,7 @@ class StrongCSV
68
68
  if self == value
69
69
  ValueResult.new(value: self, original_value: value)
70
70
  else
71
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.string.unexpected", value: value.inspect, expected: inspect, default: :"_strong_csv.literal.string.unexpected")])
71
+ ValueResult.new(original_value: value, error_messages: ["`#{inspect}` is expected, but `#{value.inspect}` was given"])
72
72
  end
73
73
  end
74
74
  end
@@ -77,12 +77,12 @@ class StrongCSV
77
77
  # @param value [Object] Value to be casted to String
78
78
  # @return [ValueResult]
79
79
  def cast(value)
80
- return ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.regexp.cant_be_casted", value: value.inspect, default: :"_strong_csv.literal.regexp.cant_be_casted")]) if value.nil?
80
+ return ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to String"]) if value.nil?
81
81
 
82
82
  if self =~ value
83
83
  ValueResult.new(value: value, original_value: value)
84
84
  else
85
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.literal.regexp.unexpected", value: value.inspect, expected: inspect, default: :"_strong_csv.literal.regexp.unexpected")])
85
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` did not match `#{inspect}`"])
86
86
  end
87
87
  end
88
88
  end
@@ -15,11 +15,11 @@ class StrongCSV
15
15
  # @param value [Object] Value to be casted to Boolean
16
16
  # @return [ValueResult]
17
17
  def cast(value)
18
- return ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.string.cant_be_casted", value: value.inspect, default: :"_strong_csv.string.cant_be_casted")]) if value.nil?
18
+ return ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to String"]) if value.nil?
19
19
 
20
20
  casted = String(value)
21
21
  if @within && !@within.cover?(casted.size)
22
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.string.out_of_range", value: value.inspect, range: @within.inspect, default: :"_strong_csv.string.out_of_range")])
22
+ ValueResult.new(original_value: value, error_messages: ["The length of `#{value.inspect}` is out of range `#{@within.inspect}`"])
23
23
  else
24
24
  ValueResult.new(value: casted, original_value: value)
25
25
  end
@@ -15,11 +15,11 @@ class StrongCSV
15
15
  # @param value [Object] Value to be casted to Time
16
16
  # @return [ValueResult]
17
17
  def cast(value)
18
- return ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.time.cant_be_casted", value: value.inspect, time_format: @format.inspect, default: :"_strong_csv.time.cant_be_casted")]) if value.nil?
18
+ return ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Time with the format `#{@format.inspect}`"]) if value.nil?
19
19
 
20
20
  ValueResult.new(value: ::Time.strptime(value.to_s, @format), original_value: value)
21
21
  rescue ArgumentError
22
- ValueResult.new(original_value: value, error_messages: [I18n.t("strong_csv.time.cant_be_casted", value: value.inspect, time_format: @format.inspect, default: :"_strong_csv.time.cant_be_casted")])
22
+ ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Time with the format `#{@format.inspect}`"])
23
23
  end
24
24
  end
25
25
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StrongCSV
4
- # ValueResult represents a CSV field is valid or not and contains its casted value if it's valid.
4
+ # ValueResult represents whether a CSV field is valid or not, and contains its casted value if it's valid.
5
5
  class ValueResult
6
6
  DEFAULT_VALUE = Object.new
7
7
  private_constant :DEFAULT_VALUE
@@ -15,11 +15,13 @@ class StrongCSV
15
15
  @error_messages = error_messages
16
16
  end
17
17
 
18
- # @return [Object] The casted value if it's valid. Otherwise, returns the original value.
18
+ # @return [::Float, ::Integer, ::Object, ::String, ::Range, Boolean, ::Time, nil] The casted value if it's valid. Otherwise, returns the original value.
19
19
  def value
20
20
  success? ? @value : @original_value
21
21
  end
22
22
 
23
+ # It returns true if the value is successfully casted.
24
+ #
23
25
  # @return [Boolean]
24
26
  def success?
25
27
  @error_messages.nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StrongCSV
4
- VERSION = "0.4.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/strong_csv.rb CHANGED
@@ -4,10 +4,8 @@ require "csv"
4
4
  require "forwardable"
5
5
  require "set"
6
6
  require "time"
7
- require "i18n"
8
7
 
9
8
  require_relative "strong_csv/version"
10
- require_relative "strong_csv/i18n"
11
9
  require_relative "strong_csv/value_result"
12
10
  require_relative "strong_csv/types/base"
13
11
  require_relative "strong_csv/types/boolean"
@@ -21,7 +19,7 @@ require_relative "strong_csv/types/union"
21
19
  require_relative "strong_csv/let"
22
20
  require_relative "strong_csv/row"
23
21
 
24
- # The top-level namespace for the strong_csv gem.
22
+ # StrongCSV is a library for parsing CSV contents with type checking.
25
23
  class StrongCSV
26
24
  class Error < StandardError; end
27
25
 
@@ -37,6 +35,12 @@ class StrongCSV
37
35
  options.delete(:nil_value)
38
36
  options = options.merge(headers: @let.headers, header_converters: :symbol)
39
37
  csv = CSV.new(csv, **options)
38
+
39
+ @let.pickers.each_value do |picker|
40
+ picker.call(csv)
41
+ csv.rewind
42
+ end
43
+
40
44
  if block_given?
41
45
  csv.each do |row|
42
46
  yield Row.new(row: row, types: @let.types, lineno: csv.lineno)
@@ -0,0 +1,163 @@
1
+ # TypeProf 0.21.2
2
+
3
+ type literals = ::Integer | ::Float | ::Range[untyped] | ::String | ::Regexp
4
+ type declarable = StrongCSV::Types::Base | literals
5
+ type casted = bool | ::Float | ::Integer | ::Range[untyped] | ::String | ::Regexp | ::Time
6
+ type column = ::Integer | ::Symbol
7
+
8
+ # Classes
9
+ class StrongCSV
10
+ VERSION: String
11
+
12
+ @let: Let
13
+
14
+ def initialize: -> void
15
+
16
+ def parse: (String | IO, **untyped) -> Array[Row]
17
+ | (String | IO, **untyped) { (Row) -> void } -> untyped
18
+
19
+ class ValueResult
20
+ DEFAULT_VALUE: Object
21
+
22
+ @value: (casted)?
23
+ @original_value: untyped
24
+
25
+ attr_reader error_messages: [::String]?
26
+
27
+ def initialize: (original_value: ::String | nil, ?value: (casted | ::Object)?, ?error_messages: [::String]?) -> void
28
+
29
+ def value: -> ((casted | ::Object | nil)?)
30
+
31
+ def success?: -> bool
32
+ end
33
+
34
+ module Types
35
+ class Base
36
+ def cast: (untyped _value) -> bot
37
+ end
38
+
39
+ class Boolean < Base
40
+ TRUE_VALUES: Set[::String]
41
+ FALSE_VALUES: Set[::String]
42
+
43
+ def cast: (::String | nil) -> ValueResult
44
+ end
45
+
46
+ class Float < Base
47
+ DEFAULT_CONSTRAINT: ^(::Float) -> true
48
+
49
+ @constraint: ^(::Float) -> boolish
50
+
51
+ def initialize: (?constraint: ^(::Float) -> boolish) -> void
52
+
53
+ def cast: (::String | nil) -> ValueResult
54
+ end
55
+
56
+ class Integer < Base
57
+ DEFAULT_CONSTRAINT: ^(::Integer) -> true
58
+
59
+ @constraint: ^(::Integer) -> boolish
60
+
61
+ def initialize: (?constraint: ^(::Integer) -> boolish) -> void
62
+
63
+ def cast: (::String | nil) -> ValueResult
64
+ end
65
+
66
+ module Literal
67
+ def cast: (untyped value) -> ValueResult
68
+ | (untyped value) -> ValueResult
69
+ | (untyped value) -> ValueResult
70
+ | (untyped value) -> ValueResult
71
+ | (untyped value) -> ValueResult
72
+ end
73
+
74
+ class Optional < Base
75
+ @type: Boolean | Float | Integer | String | Time | Literal
76
+
77
+ def initialize: (declarable) -> void
78
+
79
+ def cast: (::String | nil) -> ValueResult
80
+ end
81
+
82
+ class String < Base
83
+ @within: Range[untyped] | nil
84
+
85
+ def initialize: (?within: Range[untyped] | nil) -> void
86
+
87
+ def cast: (::String | nil) -> ValueResult
88
+ end
89
+
90
+ class Time < Base
91
+ @format: ::String
92
+
93
+ def initialize: (?format: ::String) -> void
94
+
95
+ def cast: (::String | nil) -> ValueResult
96
+ end
97
+
98
+ class Union < Base
99
+ @types: Array[untyped]
100
+
101
+ def initialize: (untyped `type`, *untyped types) -> void
102
+
103
+ def cast: (::String | nil) -> untyped
104
+ end
105
+ end
106
+
107
+ class Let
108
+ @picked: Hash[untyped, untyped]
109
+
110
+ attr_reader types: Hash[column, [declarable, ^(casted) -> untyped | nil]]
111
+ attr_reader headers: bool
112
+ attr_reader pickers: Hash[untyped, Proc]
113
+
114
+ def initialize: -> void
115
+
116
+ def let: (::String | column, declarable, *declarable) -> void
117
+ | (::String | column, declarable, *declarable) { (casted) -> untyped } -> void
118
+
119
+ def pick: (column, as: ::Symbol) { (Array[::String]) -> untyped } -> void
120
+
121
+ def integer: (**untyped) -> Types::Integer
122
+
123
+ def integer?: (**untyped) -> Types::Optional
124
+
125
+ def boolean: -> Types::Boolean
126
+
127
+ def boolean?: -> Types::Optional
128
+
129
+ def float: (**untyped) -> Types::Float
130
+
131
+ def float?: (**untyped) -> Types::Optional
132
+
133
+ def string: (**untyped) -> Types::String
134
+
135
+ def string?: (**untyped) -> Types::Optional
136
+
137
+ def time: (**untyped) -> Types::Time
138
+
139
+ def time?: (**untyped) -> Types::Optional
140
+
141
+ def optional: (*declarable) -> Types::Optional
142
+
143
+ private
144
+
145
+ def validate_columns: -> bool
146
+ end
147
+
148
+ class Row
149
+ extend Forwardable
150
+
151
+ @values: Hash[column, casted | ::Object | nil]
152
+
153
+ attr_reader errors: Hash[column, Array[::String]]
154
+ attr_reader lineno: ::Integer
155
+
156
+ def initialize: (row: Array[::String] | CSV::Row, types: Hash[column, [declarable, (^(casted | ::Object | nil) -> untyped | nil)]], lineno: ::Integer) -> void
157
+
158
+ def valid?: -> bool
159
+ end
160
+
161
+ class Error < StandardError
162
+ end
163
+ end
metadata CHANGED
@@ -1,35 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.7.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-05-10 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: i18n
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.8.11
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '2'
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.8.11
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '2'
11
+ date: 2022-07-20 00:00:00.000000000 Z
12
+ dependencies: []
33
13
  description: strong_csv is a type checker for a CSV file. It lets developers declare
34
14
  types for each column to ensure all cells are satisfied with desired types.
35
15
  email:
@@ -41,7 +21,6 @@ files:
41
21
  - LICENSE
42
22
  - README.md
43
23
  - lib/strong_csv.rb
44
- - lib/strong_csv/i18n.rb
45
24
  - lib/strong_csv/let.rb
46
25
  - lib/strong_csv/row.rb
47
26
  - lib/strong_csv/types/base.rb
@@ -55,6 +34,7 @@ files:
55
34
  - lib/strong_csv/types/union.rb
56
35
  - lib/strong_csv/value_result.rb
57
36
  - lib/strong_csv/version.rb
37
+ - sig/strong_csv.rbs
58
38
  homepage: https://github.com/yykamei/strong_csv
59
39
  licenses:
60
40
  - MIT
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- I18n.backend.store_translations(
4
- :en, {
5
- _strong_csv: {
6
- boolean: {
7
- cant_be_casted: "`%{value}` can't be casted to Boolean",
8
- },
9
- float: {
10
- cant_be_casted: "`%{value}` can't be casted to Float",
11
- },
12
- integer: {
13
- cant_be_casted: "`%{value}` can't be casted to Integer",
14
- },
15
- literal: {
16
- integer: {
17
- unexpected: "`%{expected}` is expected, but `%{value}` was given",
18
- cant_be_casted: "`%{value}` can't be casted to Integer",
19
- },
20
- float: {
21
- unexpected: "`%{expected}` is expected, but `%{value}` was given",
22
- cant_be_casted: "`%{value}` can't be casted to Float",
23
- },
24
- string: {
25
- unexpected: "`%{expected}` is expected, but `%{value}` was given",
26
- },
27
- range: {
28
- cant_be_casted: "`%{value}` can't be casted to the beginning of `%{expected}`",
29
- out_of_range: "`%{value}` is not within `%{range}`",
30
- },
31
- regexp: {
32
- cant_be_casted: "`%{value}` can't be casted to String",
33
- unexpected: "`%{value}` did not match `%{expected}`",
34
- },
35
- },
36
- string: {
37
- cant_be_casted: "`%{value}` can't be casted to String",
38
- out_of_range: "The length of `%{value}` is out of range `%{range}`",
39
- },
40
- time: {
41
- cant_be_casted: "`%{value}` can't be casted to Time with the format `%{time_format}`",
42
- },
43
- },
44
- },
45
- )