strong_csv 0.2.0 → 0.3.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: 5c718425815c3296bf644f0206eb7f351a25565908eddf5ec9b5811c045a46ec
4
- data.tar.gz: fb8912e844d913b06c026e46d0b78084f7f0b3e81738ef3cc48476d8e784d03d
3
+ metadata.gz: a367df39ccd195cbb7f62845c919f9718a4ef536de595294991fa7bc26693ca7
4
+ data.tar.gz: 220e1d69c32123c78d5a31cb63d2c9c21686bbe76feb68e4f0aeb9762601e0e3
5
5
  SHA512:
6
- metadata.gz: cc0564f9036a150e8564ff3bd947d9e4d7197b25179d37ab1c0d9997c7517834c3c76d54b5e28a6c1c5631e5c64f2601989935052b21dd6d588fa7e521ec7c8d
7
- data.tar.gz: 205d2e29e1512e1508328f724b63ec40cf1d138c8d3bf83e586f6f0d8d7730f2d8f5fae648a589650efaff1e6104c9cb56ae0b8771fb7098a34d34aa08c525ad
6
+ metadata.gz: bfe0ecbb668a07be731f9f50143812fc42d213993b4e3591e04678a4c6323347f69f18a6241d4dbfa524faa37baada63c7ffc0ab394073c1f435c914910f5d95
7
+ data.tar.gz: d345d0c12929b60d801c16e8d994fa1570f0e5fc2034f6d74de3f96bf188a7a0e85f7b9c2b1bbe61a1a0f4e4cdb4a41deddf7915cbeb540ace4b0c790c5056b0
data/README.md CHANGED
@@ -6,7 +6,7 @@ NOTE: This repository is still under development 🚧🚜🚧
6
6
 
7
7
  Type checker for a CSV file inspired by [strong_json](https://github.com/soutaro/strong_json).
8
8
 
9
- **Motivation**
9
+ ## Motivation
10
10
 
11
11
  Some applications have a feature to receive a CSV file uploaded by a user,
12
12
  and in general, it needs to validate each cell of the CSV file.
@@ -40,7 +40,18 @@ gem install strong_csv
40
40
 
41
41
  ## Usage
42
42
 
43
- TBD: This hasn't yet been implemented.
43
+ The most important APIs of strong_csv are `StrongCSV.new` and `StrongCSV#parse`.
44
+ `StrongCSV.new` lets you declare types for each CSV column with Ruby's block syntax.
45
+ Inside the block, you will mainly use `let` and declare types for a column.
46
+
47
+ After defining types, you can parse CSV content with `StrongCSV#parse`.
48
+ `StrongCSV#parse` won't raise errors as possible and just store error messages in its rows.
49
+ The reason why it won't raise errors is CSV content may contain _invalid_ rows,
50
+ but sometimes, it makes sense to ignore them and process something for _valid_ rows.
51
+ If you want to stop all processes with invalid rows,
52
+ check whether all rows are valid before proceeding with computation.
53
+
54
+ Here is an example usage of this gem:
44
55
 
45
56
  ```ruby
46
57
  require "strong_csv"
@@ -51,8 +62,7 @@ strong_csv = StrongCSV.new do
51
62
  let :name, string(within: 1..255)
52
63
  let :description, string?(within: 1..1000)
53
64
  let :active, boolean
54
- let :started_at, time?
55
- let :data, any?
65
+ let :started_at, time?(format: "%Y-%m-%dT%H:%M:%S")
56
66
 
57
67
  # Literal declaration
58
68
  let :status, 0..6
@@ -68,10 +78,12 @@ strong_csv = StrongCSV.new do
68
78
  end
69
79
  end
70
80
 
81
+ # TODO: The followings are not implemented so far.
82
+
71
83
  # Regular expressions
72
84
  let :url, %r{\Ahttps://}
73
85
 
74
- # Custom validation.
86
+ # Custom validation
75
87
  #
76
88
  # This example sees the database to fetch exactly stored `User` IDs,
77
89
  # and it checks the `:user_id` cell really exists in the `users` table.
@@ -101,24 +113,304 @@ end
101
113
 
102
114
  ## Available types
103
115
 
104
- | Type | Arguments | Description | Example |
105
- | ------------------------ | --------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
106
- | `integer` | | The value must be casted to Integer | `let :stock, integer` |
107
- | `integer?` | | The value can be `nil`. If the value exists, it must satisfy `integer` constraint. | `let :stock, integer?` |
108
- | `float` | | The value must be casted to Float | `let :rate, float` |
109
- | `float?` | | The value can be `nil`. If the value exists, it must satisfy `float` constraint. | `let :rate, float?` |
110
- | `boolean` | | The value must be casted to Boolean | `let :active, boolean` |
111
- | `boolean?` | | The value can be `nil`. If the value exists, it must satisfy `boolean` constraint. | `let :active, boolean?` |
112
- | `string` | `:within` | The value must be casted to String | `let :name, string(within: 1..255)` |
113
- | `string?` | `:within` | The value can be `nil`. If the value exists, it must satisfy `string` constraint. | `let :name, string?(within: 1..255)` |
114
- | `time` | `:format` | The value must be casted to Time | `let :started_at, time(format: "%Y-%m-%dT%%H:%M:%S")` |
115
- | `time?` | `:format` | The value can be `nil`. If the value exists, it must satisfy `time` constraint. | `let :started_at, time?(format: "%Y-%m-%dT%%H:%M:%S")` |
116
- | `optional` | `type` | The value can be `nil`. If the value exists, it must satisfy the given type constraint. | `let :foo, optional(123)` |
117
- | `23` (Integer literal) | | The value must be casted to the specific Integer literal | `let :id, 3` |
118
- | `15.12` (Float literal) | | The value must be casted to the specific Float literal | `let :id, 3.8` |
119
- | `1..10` (Range literal) | | The value must be casted to the beginning of Range and be covered with it | `let :id, 10..30`, `let :id, 1.0..30`, `let :id, "a".."z"` |
120
- | `"abc"` (String literal) | | The value must be casted to the specific String literal | `let :drink, "coffee"` |
121
- | , (Union type) | | The value must satisfy one of the subtypes | `let :id, 1, 2, 3` |
116
+ <table>
117
+ <tr>
118
+ <th>Type</th>
119
+ <th>Description</th>
120
+ </tr>
121
+ <tr>
122
+ <td><a href="#integer-and-integer"><code>integer</code> and <code>integer?</code></a></td>
123
+ <td>The value must be casted to <code>Integer</code>.</td>
124
+ </tr>
125
+ <tr>
126
+ <td><a href="#float-and-float"><code>float</code> and <code>float?</code></a></td>
127
+ <td>The value must be casted to <code>Float</code>.</td>
128
+ </tr>
129
+ <tr>
130
+ <td><a href="#boolean-and-boolean"><code>boolean</code> and <code>boolean?</code></a></td>
131
+ <td>The value must be casted to Boolean (<code>true</code> or <code>false</code>).</td>
132
+ </tr>
133
+ <tr>
134
+ <td><a href="#string-and-string"><code>string</code> and <code>string?</code></a></td>
135
+ <td>The value must be casted to <code>String</code>.</td>
136
+ </tr>
137
+ <tr>
138
+ <td><a href="#time-and-time"><code>time</code> and <code>time?</code></a></td>
139
+ <td>The value must be casted to <code>Time</code>.</td>
140
+ </tr>
141
+ <tr>
142
+ <td><a href="#optional"><code>optional</code></a></td>
143
+ <td>The value can be <code>nil</code>. If the value exists, it must satisfy the given type constraint.</td>
144
+ </tr>
145
+ <tr>
146
+ <td><a href="#literal"><code>23</code> (Integer literal)</a></td>
147
+ <td>The value must be casted to the specific <code>Integer</code> literal.</td>
148
+ </tr>
149
+ <tr>
150
+ <td><a href="#literal"><code>15.12</code> (Float literal)</a></td>
151
+ <td>The value must be casted to the specific <code>Float</code> literal.</td>
152
+ </tr>
153
+ <tr>
154
+ <td><a href="#literal"><code>1..10</code> (Range literal)</a></td>
155
+ <td>The value must be casted to the beginning of <code>Range</code> and be covered with it.</td>
156
+ </tr>
157
+ <tr>
158
+ <td><a href="#literal"><code>"abc"</code> (String literal)</a></td>
159
+ <td>The value must be casted to the specific <code>String</code> literal.</td>
160
+ </tr>
161
+ <tr>
162
+ <td><a href="#union"><code>,</code> (Union type)</a></td>
163
+ <td>The value must satisfy one of the subtypes.</td>
164
+ </tr>
165
+ </table>
166
+
167
+ ### `integer` and `integer?`
168
+
169
+ The value must be casted to Integer. `integer?` allows the value to be `nil`, so you can declare optional integer type
170
+ for columns.
171
+
172
+ _Example_
173
+
174
+ ```ruby
175
+ strong_csv = StrongCSV.new do
176
+ let :stock, integer
177
+ let :state, integer?
178
+ end
179
+
180
+ result = strong_csv.parse(<<~CSV)
181
+ stock,state
182
+ 12,0
183
+ 20,
184
+ non-integer,1
185
+ CSV
186
+
187
+ result.map(&:valid?) # => [true, true, false]
188
+ result[0].slice(:stock, :state) # => {:stock=>12, :state=>0}
189
+ result[1].slice(:stock, :state) # => {:stock=>20, :state=>nil}
190
+ result[2].slice(:stock, :state) # => {:stock=>"non-integer", :state=>1} ("non-integer" cannot be casted to Integer)
191
+ ```
192
+
193
+ ### `float` and `float?`
194
+
195
+ The value must be casted to Float. `float?` allows the value to be `nil`, so you can declare optional float type for
196
+ columns.
197
+
198
+ _Example_
199
+
200
+ ```ruby
201
+ strong_csv = StrongCSV.new do
202
+ let :tax_rate, float
203
+ let :fail_rate, float?
204
+ end
205
+
206
+ result = strong_csv.parse(<<~CSV)
207
+ tax_rate,fail_rate
208
+ 0.02,0.1
209
+ 0.05,
210
+ ,0.8
211
+ CSV
212
+
213
+ result.map(&:valid?) # => [true, true, false]
214
+ result[0].slice(:tax_rate, :fail_rate) # => {:tax_rate=>0.02, :fail_rate=>0.1}
215
+ result[1].slice(:tax_rate, :fail_rate) # => {:tax_rate=>0.05, :fail_rate=>nil}
216
+ result[2].slice(:tax_rate, :fail_rate) # => {:tax_rate=>nil, :fail_rate=>0.8} (`nil` is not allowed for `tax_rate`)
217
+ ```
218
+
219
+ ### `boolean` and `boolean?`
220
+
221
+ The value must be casted to Boolean (`true` of `false`).
222
+ `"true"`, `"True"`, and `"TRUE"` are casted to `true`,
223
+ while `"false"`, `"False"`, and `"FALSE"` are casted to `false`.
224
+ `boolean?` allows the value to be `nil` as an optional boolean
225
+ value.
226
+
227
+ _Example_
228
+
229
+ ```ruby
230
+ strong_csv = StrongCSV.new do
231
+ let :enabled, boolean
232
+ let :active, boolean?
233
+ end
234
+
235
+ result = strong_csv.parse(<<~CSV)
236
+ enabled,active
237
+ True,True
238
+ False,
239
+ ,
240
+ CSV
241
+
242
+ result.map(&:valid?) # => [true, true, false]
243
+ result[0].slice(:enabled, :active) # => {:enabled=>true, :active=>true}
244
+ result[1].slice(:enabled, :active) # => {:enabled=>false, :active=>nil}
245
+ result[2].slice(:enabled, :active) # => {:enabled=>nil, :active=>nil} (`nil` is not allowed for `enabled`)
246
+ ```
247
+
248
+ ### `string` and `string?`
249
+
250
+ The value must be casted to String. `string?` allows the value to be `nil` as an optional string value.
251
+ They also support `:within` in its arguments, and it limits the length of the string value within the specified `Range`.
252
+
253
+ _Example_
254
+
255
+ ```ruby
256
+ strong_csv = StrongCSV.new do
257
+ let :name, string(within: 1..4)
258
+ let :description, string?
259
+ end
260
+
261
+ result = strong_csv.parse(<<~CSV)
262
+ name,description
263
+ JB,Hello
264
+ yykamei,
265
+ ,🤷
266
+ CSV
267
+
268
+ result.map(&:valid?) # => [true, false, false]
269
+ result[0].slice(:name, :description) # => {:name=>"JB", :description=>"Hello"}
270
+ result[1].slice(:name, :description) # => {:name=>"yykamei", :description=>nil} ("yykamei" exceeds the `Range` specified with `:within`)
271
+ result[2].slice(:name, :description) # => {:name=>nil, :description=>"🤷"} (`nil` is not allowed for `name`)
272
+ ```
273
+
274
+ ### `time` and `time?`
275
+
276
+ The value must be casted to Time. `time?` allows the value to be `nil` as an optional time value.
277
+ They have the `:format` argument, which is used as the format
278
+ of [`Time.strptime`](https://rubydoc.info/stdlib/time/Time.strptime);
279
+ it means you can ensure the value must satisfy the time format. The default value of `:format` is `"%Y-%m-%d"`.
280
+
281
+ _Example_
282
+
283
+ ```ruby
284
+ strong_csv = StrongCSV.new do
285
+ let :start_on, time
286
+ let :updated_at, time?(format: "%FT%T")
287
+ end
288
+
289
+ result = strong_csv.parse(<<~CSV)
290
+ start_on,updated_at
291
+ 2022-04-01,2022-04-30T15:30:59
292
+ 2022-05-03
293
+ 05-03,2021-09-03T09:48:23
294
+ CSV
295
+
296
+ result.map(&:valid?) # => [true, true, false]
297
+ result[0].slice(:start_on, :updated_at) # => {:start_on=>2022-04-01 00:00:00 +0900, :updated_at=>2022-04-30 15:30:59 +0900}
298
+ result[1].slice(:start_on, :updated_at) # => {:start_on=>2022-05-03 00:00:00 +0900, :updated_at=>nil}
299
+ result[2].slice(:start_on, :updated_at) # => {:start_on=>"05-03", :updated_at=>2021-09-03 09:48:23 +0900} ("05-03" does not satisfy the default format `"%Y-%m-%d"`)
300
+ ```
301
+
302
+ ### `optional`
303
+
304
+ While each type above has its optional type with `?`, literals cannot be suffixed with `?`.
305
+ However, there would be a case to have an optional literal type.
306
+ In this case, `optional` might be useful and lets you declare such types.
307
+
308
+ _Example_
309
+
310
+ ```ruby
311
+ strong_csv = StrongCSV.new do
312
+ let :foo, optional(123)
313
+ let :bar, optional("test")
314
+ end
315
+
316
+ result = strong_csv.parse(<<~CSV)
317
+ foo,bar
318
+ 123,test
319
+ ,
320
+ 124
321
+ CSV
322
+
323
+ result.map(&:valid?) # => [true, true, false]
324
+ result[0].slice(:foo, :bar) # => {:foo=>123, :bar=>"test"}
325
+ result[1].slice(:foo, :bar) # => {:foo=>nil, :bar=>nil}
326
+ result[2].slice(:foo, :bar) # => {:foo=>"124", :bar=>nil} (124 is not equal to 123)
327
+ ```
328
+
329
+ ### Literal
330
+
331
+ You can declare literal value as types. The supported literals are `Integer`, `Float`, `String`, and `Range`.
332
+
333
+ _Example_
334
+
335
+ ```ruby
336
+ strong_csv = StrongCSV.new do
337
+ let 0, 123
338
+ let 1, "test"
339
+ let 2, 2.5
340
+ let 3, 1..10
341
+ end
342
+
343
+ result = strong_csv.parse(<<~CSV)
344
+ 123,test,2.5,9
345
+ 123,test,2.5,0
346
+ 123,Hey,2.5,10
347
+ CSV
348
+
349
+ result.map(&:valid?) # => [true, false, false]
350
+ result[0].slice(0, 1, 2, 3) # => {0=>123, 1=>"test", 2=>2.5, 3=>9}
351
+ result[1].slice(0, 1, 2, 3) # => {0=>123, 1=>"test", 2=>2.5, 3=>"0"} (0 is out of 1..10)
352
+ result[2].slice(0, 1, 2, 3) # => {0=>123, 1=>"Hey", 2=>2.5, 3=>10} ("Hey" is not equal to "test")
353
+ ```
354
+
355
+ ### Union
356
+
357
+ There would be a case that it's alright if a value satisfies one of the types.
358
+ Union types are useful for such a case.
359
+
360
+ _Example_
361
+
362
+ ```ruby
363
+ strong_csv = StrongCSV.new do
364
+ let :priority, 10, 20, 30
365
+ let :size, "S", "M", "L"
366
+ end
367
+
368
+ result = strong_csv.parse(<<~CSV)
369
+ priority,size
370
+ 10,M
371
+ 30,A
372
+ 11,S
373
+ CSV
374
+
375
+ result.map(&:valid?) # => [true, false, false]
376
+ result[0].slice(:priority, :size) # => {:priority=>10, :size=>"M"}
377
+ result[1].slice(:priority, :size) # => {:priority=>30, :size=>"A"} ("A" is not one of "S", "M", and "L")
378
+ result[2].slice(:priority, :size) # => {:priority=>"11", :size=>"S"} (11 is not one of 10, 20, and 30)
379
+ ```
380
+
381
+ ## I18n (Internationalization)
382
+
383
+ strong_csv depends on [i18n](https://rubygems.org/gems/i18n) for internationalization.
384
+ If you want to have a locale-specific error message, put the message catalog in your locale files.
385
+ Here is an example of a locale file.
386
+
387
+ ```yaml
388
+ ja:
389
+ strong_csv:
390
+ boolean:
391
+ cant_be_casted: "`%{value}`はBooleanに変換できません"
392
+ float:
393
+ cant_be_casted: "`%{value}`はFloatに変換できません"
394
+ integer:
395
+ cant_be_casted: "`%{value}`はIntegerに変換できません"
396
+ literal:
397
+ integer:
398
+ unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
399
+ cant_be_casted: "`%{expected}`ではなく`%{value}`が入力されています"
400
+ float:
401
+ unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
402
+ cant_be_casted: "`%{value}`はFloatに変換できません"
403
+ string:
404
+ unexpected: "`%{expected}`ではなく`%{value}`が入力されています"
405
+ range:
406
+ cant_be_casted: "`%{value}`は`%{expected}`の始端に変換できません"
407
+ out_of_range: "`%{value}`は`%{range}`の範囲外です"
408
+ string:
409
+ cant_be_casted: "`%{value}`はStringに変換できません"
410
+ out_of_range: "`%{value}`の文字数は`%{range}`の範囲外です"
411
+ time:
412
+ cant_be_casted: "`%{value}`は`%{time_format}`でTimeに変換できません"
413
+ ```
122
414
 
123
415
  ## Contributing
124
416
 
@@ -0,0 +1,41 @@
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
+ },
32
+ string: {
33
+ cant_be_casted: "`%{value}` can't be casted to String",
34
+ out_of_range: "The length of `%{value}` is out of range `%{range}`",
35
+ },
36
+ time: {
37
+ cant_be_casted: "`%{value}` can't be casted to Time with the format `%{time_format}`",
38
+ },
39
+ },
40
+ },
41
+ )
@@ -6,7 +6,7 @@ class StrongCSV
6
6
  extend Forwardable
7
7
  using Types::Literal
8
8
 
9
- def_delegators :@values, :[], :fetch
9
+ def_delegators :@values, :[], :fetch, :slice
10
10
 
11
11
  # @return [Hash]
12
12
  attr_reader :errors
@@ -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: ["`#{value.inspect}` can't be casted to Boolean"])
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")])
21
21
  end
22
22
  end
23
23
  end
@@ -10,7 +10,7 @@ class StrongCSV
10
10
  def cast(value)
11
11
  ValueResult.new(value: Float(value), original_value: value)
12
12
  rescue ArgumentError, TypeError
13
- ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Float"])
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")])
14
14
  end
15
15
  end
16
16
  end
@@ -10,7 +10,7 @@ class StrongCSV
10
10
  def cast(value)
11
11
  ValueResult.new(value: Integer(value), original_value: value)
12
12
  rescue ArgumentError, TypeError
13
- ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to Integer"])
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")])
14
14
  end
15
15
  end
16
16
  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: ["`#{inspect}` is expected, but `#{int}` was given"])
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")])
17
17
  end
18
18
  rescue ArgumentError, TypeError
19
- ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to #{self.class.name}"])
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")])
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: ["`#{inspect}` is expected, but `#{float}` was given"])
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")])
32
32
  end
33
33
  rescue ArgumentError, TypeError
34
- ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to #{self.class.name}"])
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")])
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: ["`nil` can't be casted to the beginning of `#{inspect}`"]) if value.nil?
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?
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: ["`#{casted.inspect}` is not within `#{inspect}`"])
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")])
58
58
  end
59
59
  rescue ArgumentError
60
- ValueResult.new(original_value: value, error_messages: ["`#{value.inspect}` can't be casted to the beginning of `#{inspect}`"])
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")])
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: ["`#{inspect}` is expected, but `#{value.inspect}` was given"])
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")])
72
72
  end
73
73
  end
74
74
  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: ["`#{value.inspect}` can't be casted to String"]) if value.nil?
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?
19
19
 
20
20
  casted = String(value)
21
21
  if @within && !@within.cover?(casted.size)
22
- ValueResult.new(original_value: value, error_messages: ["`#{casted.inspect}` is out of range `#{@within.inspect}`"])
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")])
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: ["`#{value.inspect}` can't be casted to Time"]) if value.nil?
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?
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: ["`#{value.inspect}` can't be casted to Time with the format `#{@format}`"])
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")])
23
23
  end
24
24
  end
25
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StrongCSV
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/strong_csv.rb CHANGED
@@ -4,8 +4,10 @@ require "csv"
4
4
  require "forwardable"
5
5
  require "set"
6
6
  require "time"
7
+ require "i18n"
7
8
 
8
9
  require_relative "strong_csv/version"
10
+ require_relative "strong_csv/i18n"
9
11
  require_relative "strong_csv/value_result"
10
12
  require_relative "strong_csv/types/base"
11
13
  require_relative "strong_csv/types/boolean"
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-04-29 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-05-04 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'
13
33
  description: strong_csv is a type checker for a CSV file. It lets developers declare
14
34
  types for each column to ensure all cells are satisfied with desired types.
15
35
  email:
@@ -21,6 +41,7 @@ files:
21
41
  - LICENSE
22
42
  - README.md
23
43
  - lib/strong_csv.rb
44
+ - lib/strong_csv/i18n.rb
24
45
  - lib/strong_csv/let.rb
25
46
  - lib/strong_csv/row.rb
26
47
  - lib/strong_csv/types/base.rb