strong_csv 0.4.0 → 0.7.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 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
- )