strong_csv 0.2.0 → 0.3.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: 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