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 +4 -4
- data/README.md +315 -23
- data/lib/strong_csv/i18n.rb +41 -0
- data/lib/strong_csv/row.rb +1 -1
- data/lib/strong_csv/types/boolean.rb +1 -1
- data/lib/strong_csv/types/float.rb +1 -1
- data/lib/strong_csv/types/integer.rb +1 -1
- data/lib/strong_csv/types/literal.rb +8 -8
- data/lib/strong_csv/types/string.rb +2 -2
- data/lib/strong_csv/types/time.rb +2 -2
- data/lib/strong_csv/version.rb +1 -1
- data/lib/strong_csv.rb +2 -0
- metadata +24 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a367df39ccd195cbb7f62845c919f9718a4ef536de595294991fa7bc26693ca7
|
|
4
|
+
data.tar.gz: 220e1d69c32123c78d5a31cb63d2c9c21686bbe76feb68e4f0aeb9762601e0e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
)
|
data/lib/strong_csv/row.rb
CHANGED
|
@@ -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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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
|
data/lib/strong_csv/version.rb
CHANGED
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.
|
|
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
|
|
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
|