rcsv 0.0.9 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjlmMDhlZWFkNzk3ZmJkYjc1Zjk4ZjFmM2FjYjc5YTVmNzZhOWYyOA==
5
+ data.tar.gz: !binary |-
6
+ ZjFkZDVmMDc0ZmJkODg2YzMzYzU2MjViODVkNjNhZDJhMjcxZWI0ZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZmZjZjAzY2VlNjU4MTM0ZDBmZWQwY2NiNTZmNWY4NzBjMzdlNTBhMjJhZjQz
10
+ ODEzNDc3MjM5ZGJjNjE5MTgxMTQ3MjU3ZTU3YmQwZmQwNWZmNWFkMzlkNzZl
11
+ ZWExNTVmODFjOGJkNGIwNmFlMDI5MDA5OWY1ODBiNjhiMWNlZmU=
12
+ data.tar.gz: !binary |-
13
+ YjZhOWIwZjI0OGMwOTg3NjY1MzgxMjMwYTU4NmFkMzQ3MGU2MWZmNTFjY2Q4
14
+ MGU5MzM3M2EwOWEwNjVkMzllYzgzM2U2OTQwYTA5ZWZiMDVkMjBjZDJjYTIy
15
+ NzJiYTY1MzYyM2I0MzBkYjc3OTRkYmQwNjMwZDUyNGJkZmI1MWU=
@@ -2,6 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 1.9.2
5
- - ruby-head
5
+ # - ruby-head
6
+ - 2.0.0
6
7
  - 1.8.7
7
8
  - ree
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rcsv (0.0.9)
4
+ rcsv (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -40,7 +40,7 @@ First, check out the master branch. Then cd there and run:
40
40
 
41
41
  ## Usage
42
42
 
43
- Currently, Rcsv only supports CSV parsing. CSV write support is planned.
43
+ Currently, Rcsv only supports CSV parsing. CSV write support is under works.
44
44
 
45
45
  Quickstart:
46
46
 
@@ -89,7 +89,8 @@ If CSV has a header, :columns keys can be strings that are equal to column names
89
89
  * :alias - Object of any type (though usually a Symbol) that is used as a key that represents column name when :row_as_hash is set.
90
90
  * :type - A Ruby Symbol that specifies Ruby data type that CSV cell value should be converted into. Supported types: :int, :float, :string, :bool. :string is the default.
91
91
  * :default - Object of any type (though usually of the same type that is specified by :type option). If CSV doesn't have any value for a cell, this default value is used.
92
- * :match - A string. If set, makes Rcsv skip all the rows where any column doesn't match its :match value. Useful for filtering data.
92
+ * :match - An array of Ruby objects of supported type (see :type). If set, makes Rcsv skip all the rows where any column isn't included in its :match value. Useful for filtering data.
93
+ * :not_match - An array of Ruby objects of supported type (see :type). If set, makes Rcsv skip all the rows where any column is included in its :not_match value. Useful for skipping data and is an opposite of :match.
93
94
 
94
95
 
95
96
  ### :header
@@ -116,13 +117,13 @@ Specifies a number of bytes that are read at once, thus allowing to read drectly
116
117
 
117
118
  ## Examples
118
119
 
119
- This example parses a 3-column CSV file and only returns parsed rows where "Age" values are set to "35".
120
+ This example parses a 3-column CSV file and only returns parsed rows where "Age" values are parsed to 35, 36 or 37.
120
121
 
121
122
  Rcsv.parse some_csv, :row_as_hash => true,
122
123
  :columns => {
123
124
  'First Name' => { :alias => :first_name, :default => "Unknown" },
124
125
  'Last Name' => { :alias => :last_name, :default => "Unknown"},
125
- 'Age' => { :alias => :age, :type => :int, :match => "35"}
126
+ 'Age' => { :alias => :age, :type => :int, :match => [35, 36, 37]]
126
127
  }
127
128
 
128
129
  The result would look like this:
@@ -170,9 +171,9 @@ This way it is possible to read from a File directly, with a 20MiB buffer and pa
170
171
 
171
172
  ## To do
172
173
 
173
- * More specs for boolean values
174
- * Specs for Ruby parse
175
- * Add CSV write support
174
+ * More tests for boolean values
175
+ * More tests for Ruby parse
176
+ * Finish CSV write support
176
177
 
177
178
 
178
179
  ## Contributing
data/RELNOTES CHANGED
@@ -1,3 +1,12 @@
1
+ Version 0.1.1
2
+ * added :not_match filter that skips rows with values listed in that option
3
+ * eliminated many potential memory leaks related to exception handling
4
+
5
+ Version 0.1.0
6
+ * backwards-incompatible change in :match option: filtering now happens after creating Ruby objects and thus rows can now be filtered not only as CSV field strings
7
+ * fixed a bug where field's key was being set to nil if :row_as_hash was enabled and :alias wasn't defined
8
+ * fixed assert_equal parameters order in tests
9
+
1
10
  Version 0.0.9
2
11
  * preliminary write support
3
12
 
@@ -12,22 +12,24 @@ static VALUE rcsv_parse_error; /* class Rcsv::ParseError << StandardError; end *
12
12
  struct rcsv_metadata {
13
13
  /* Derived from user-specified options */
14
14
  bool row_as_hash; /* Used to return array of hashes rather than array of arrays */
15
- bool empty_field_is_nil; /* Do we convert empty fields to nils? */
15
+ bool empty_field_is_nil; /* Do we convert empty fields to nils? */
16
16
  size_t offset_rows; /* Number of rows to skip before parsing */
17
17
 
18
18
  char * row_conversions; /* A pointer to string/array of row conversions char specifiers */
19
- char ** only_rows; /* A pointer to array of strings for only_rows filter */
19
+ VALUE * only_rows; /* A pointer to array of row filters */
20
+ VALUE * except_rows; /* A pointer to array of negative row filters */
20
21
  VALUE * row_defaults; /* A pointer to array of row defaults */
21
22
  VALUE * column_names; /* A pointer to array of column names to be used with hashes */
22
23
 
23
24
  /* Pointer options lengths */
24
25
  size_t num_row_conversions; /* Number of converter types in row_conversions array */
25
26
  size_t num_only_rows; /* Number of items in only_rows filter */
27
+ size_t num_except_rows; /* Number of items in except_rows filter */
26
28
  size_t num_row_defaults; /* Number of default values in row_defaults array */
27
29
  size_t num_columns; /* Number of columns detected from column_names.size */
28
30
 
29
31
  /* Internal state */
30
- bool skip_current_row; /* Used by only_rows filter to skip parsing of the row remainder */
32
+ bool skip_current_row; /* Used by only_rows and except_rows filters to skip parsing of the row remainder */
31
33
  size_t current_col; /* Current column's index */
32
34
  size_t current_row; /* Current row's index */
33
35
 
@@ -55,16 +57,6 @@ void end_of_field_callback(void * field, size_t field_size, void * data) {
55
57
  return;
56
58
  }
57
59
 
58
- /* Filter by string row values listed in meta->only_rows */
59
- if ((meta->only_rows != NULL) &&
60
- (field_str != NULL) && /* TODO: What if we want to filter out NULLs? */
61
- (meta->current_col < meta->num_only_rows) &&
62
- (meta->only_rows[meta->current_col] != NULL) &&
63
- (strcmp(meta->only_rows[meta->current_col], field_str))) {
64
- meta->skip_current_row = true;
65
- return;
66
- }
67
-
68
60
  /* Get row conversion char specifier */
69
61
  if (meta->current_col < meta->num_row_conversions) {
70
62
  row_conversion = (char)meta->row_conversions[meta->current_col];
@@ -130,6 +122,24 @@ void end_of_field_callback(void * field, size_t field_size, void * data) {
130
122
  }
131
123
  }
132
124
 
125
+ /* Filter by row values listed in meta->only_rows */
126
+ if ((meta->only_rows != NULL) &&
127
+ (meta->current_col < meta->num_only_rows) &&
128
+ (meta->only_rows[meta->current_col] != Qnil) &&
129
+ (!rb_ary_includes(meta->only_rows[meta->current_col], parsed_field))) {
130
+ meta->skip_current_row = true;
131
+ return;
132
+ }
133
+
134
+ /* Filter out by row values listed in meta->except_rows */
135
+ if ((meta->except_rows != NULL) &&
136
+ (meta->current_col < meta->num_except_rows) &&
137
+ (meta->except_rows[meta->current_col] != Qnil) &&
138
+ (rb_ary_includes(meta->except_rows[meta->current_col], parsed_field))) {
139
+ meta->skip_current_row = true;
140
+ return;
141
+ }
142
+
133
143
  /* Assign the value to appropriate hash key if parsing into Hash */
134
144
  if (meta->row_as_hash) {
135
145
  if (meta->current_col >= meta->num_columns) {
@@ -187,110 +197,136 @@ void end_of_line_callback(int last_char, void * data) {
187
197
  return;
188
198
  }
189
199
 
190
- void custom_end_of_line_callback(int last_char, void * data) {
191
- struct rcsv_metadata * meta = (struct rcsv_metadata *) data;
192
-
193
- if (!meta->skip_current_row) {
200
+ /* Filter rows need to be either nil or Arrays of Strings/bools/nil, so we validate this here */
201
+ VALUE validate_filter_row(const char * filter_name, VALUE row) {
202
+ if (row == Qnil) {
203
+ return Qnil;
204
+ } else if (TYPE(row) == T_ARRAY) {
205
+ size_t j;
206
+ for (j = 0; j < (size_t)RARRAY_LEN(row); j++) {
207
+ switch (TYPE(rb_ary_entry(row, j))) {
208
+ case T_NIL:
209
+ case T_TRUE:
210
+ case T_FALSE:
211
+ case T_FLOAT:
212
+ case T_FIXNUM:
213
+ case T_BIGNUM:
214
+ case T_REGEXP:
215
+ case T_STRING:
216
+ break;
217
+ default:
218
+ rb_raise(rcsv_parse_error,
219
+ ":%s can only accept nil or Array consisting of String, boolean or nil elements, but %s was provided.",
220
+ filter_name, RSTRING_PTR(rb_inspect(row)));
221
+ }
222
+ }
223
+ return row;
224
+ } else {
225
+ rb_raise(rcsv_parse_error,
226
+ ":%s can only accept nil or Array as an element, but %s was provided.",
227
+ filter_name, RSTRING_PTR(rb_inspect(row)));
194
228
  }
195
229
  }
196
230
 
197
- /* C API */
198
-
199
- /* The main method that handles parsing */
200
- static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
201
- struct rcsv_metadata meta;
202
- VALUE csvio, csvstr, buffer_size, options, option;
231
+ /* All the possible free()'s should be listed here.
232
+ This function should be invoked before returning the result to Ruby or raising an exception. */
233
+ void free_memory(struct csv_parser * cp, struct rcsv_metadata * meta) {
234
+ if (meta->only_rows != NULL) {
235
+ free(meta->only_rows);
236
+ }
203
237
 
204
- struct csv_parser cp;
205
- unsigned char csv_options = CSV_STRICT_FINI | CSV_APPEND_NULL;
206
- char * csv_string;
207
- size_t csv_string_len;
208
- int error;
209
- size_t i = 0;
238
+ if (meta->except_rows != NULL) {
239
+ free(meta->except_rows);
240
+ }
210
241
 
211
- /* Setting up some sane defaults */
212
- meta.row_as_hash = false;
213
- meta.empty_field_is_nil = false;
214
- meta.skip_current_row = false;
215
- meta.num_columns = 0;
216
- meta.current_col = 0;
217
- meta.current_row = 0;
218
- meta.offset_rows = 0;
219
- meta.num_only_rows = 0;
220
- meta.num_row_defaults = 0;
221
- meta.num_row_conversions = 0;
222
- meta.only_rows = NULL;
223
- meta.row_defaults = NULL;
224
- meta.row_conversions = NULL;
225
- meta.column_names = NULL;
226
- meta.result = (VALUE[]){rb_ary_new()}; /* [] */
242
+ if (meta->row_defaults != NULL) {
243
+ free(meta->row_defaults);
244
+ }
227
245
 
228
- /* csvio is required, options is optional (pun intended) */
229
- rb_scan_args(argc, argv, "11", &csvio, &options);
246
+ if (meta->column_names != NULL) {
247
+ free(meta->column_names);
248
+ }
230
249
 
231
- /* options ||= nil */
232
- if (NIL_P(options)) {
233
- options = rb_hash_new();
250
+ if (cp != NULL) {
251
+ csv_free(cp);
234
252
  }
253
+ }
235
254
 
236
- buffer_size = rb_hash_aref(options, ID2SYM(rb_intern("buffer_size")));
255
+ /* An rb_rescue()-compatible free_memory() wrapper that unpacks C pointers from Ruby's Fixnums */
256
+ VALUE rcsv_free_memory(VALUE ensure_container) {
257
+ free_memory(
258
+ (struct csv_parser *)NUM2LONG(rb_ary_entry(ensure_container, 3)),
259
+ (struct rcsv_metadata *)NUM2LONG(rb_ary_entry(ensure_container, 2))
260
+ );
237
261
 
238
- /* By default, parsing is strict */
239
- option = rb_hash_aref(options, ID2SYM(rb_intern("nostrict")));
240
- if (!option || (option == Qnil)) {
241
- csv_options |= CSV_STRICT;
242
- }
262
+ return Qnil;
263
+ }
243
264
 
244
- /* By default, empty strings are treated as Nils and quoted empty strings are treated as empty Ruby strings */
245
- option = rb_hash_aref(options, ID2SYM(rb_intern("parse_empty_fields_as")));
246
- if ((option == Qnil) || (option == ID2SYM(rb_intern("nil_or_string")))) {
247
- csv_options |= CSV_EMPTY_IS_NULL;
248
- } else if (option == ID2SYM(rb_intern("nil"))) {
249
- meta.empty_field_is_nil = true;
250
- } else if (option == ID2SYM(rb_intern("string"))) {
251
- meta.empty_field_is_nil = false;
252
- } else {
253
- rb_raise(rcsv_parse_error, "The only valid options for :parse_empty_fields_as are :nil, :string and :nil_or_string, but %s was supplied.", RSTRING_PTR(rb_inspect(option)));
254
- }
265
+ /* An rb_rescue()-compatible Ruby pseudo-method that handles the actual parsing */
266
+ VALUE rcsv_raw_parse(VALUE ensure_container) {
267
+ /* Unpacking multiple variables from a single Ruby VALUE */
268
+ VALUE options = rb_ary_entry(ensure_container, 0);
269
+ VALUE csvio = rb_ary_entry(ensure_container, 1);
270
+ struct rcsv_metadata * meta = (struct rcsv_metadata *)NUM2LONG(rb_ary_entry(ensure_container, 2));
271
+ struct csv_parser * cp = (struct csv_parser *)NUM2LONG(rb_ary_entry(ensure_container, 3));
255
272
 
256
- /* Try to initialize libcsv */
257
- if (csv_init(&cp, csv_options) == -1) {
258
- rb_raise(rcsv_parse_error, "Couldn't initialize libcsv");
259
- }
273
+ /* Helper temporary variables */
274
+ VALUE option, csvstr, buffer_size;
275
+
276
+ /* libcsv-related temporary variables */
277
+ char * csv_string;
278
+ size_t csv_string_len;
279
+ int error;
280
+
281
+ /* Generic iterator */
282
+ size_t i = 0;
283
+
284
+ /* IO buffer size can be controller via an option */
285
+ buffer_size = rb_hash_aref(options, ID2SYM(rb_intern("buffer_size")));
260
286
 
261
287
  /* By default, parse as Array of Arrays */
262
288
  option = rb_hash_aref(options, ID2SYM(rb_intern("row_as_hash")));
263
289
  if (option && (option != Qnil)) {
264
- meta.row_as_hash = true;
290
+ meta->row_as_hash = true;
265
291
  }
266
292
 
267
293
  /* :col_sep sets the column separator, default is comma (,) */
268
294
  option = rb_hash_aref(options, ID2SYM(rb_intern("col_sep")));
269
295
  if (option != Qnil) {
270
- csv_set_delim(&cp, (unsigned char)*StringValuePtr(option));
296
+ csv_set_delim(cp, (unsigned char)*StringValuePtr(option));
271
297
  }
272
298
 
273
299
  /* Specify how many rows to skip from the beginning of CSV */
274
300
  option = rb_hash_aref(options, ID2SYM(rb_intern("offset_rows")));
275
301
  if (option != Qnil) {
276
- meta.offset_rows = (size_t)NUM2INT(option);
302
+ meta->offset_rows = (size_t)NUM2INT(option);
277
303
  }
278
304
 
279
- /* :only_rows is a string mask where row is only parsed
305
+ /* :only_rows is a list of values where row is only parsed
280
306
  if its fields match those in the passed array.
281
- [nil, nil, "ABC"] skips all rows where 3rd column isn't equal to "ABC" */
307
+ [nil, nil, ["ABC", nil, 1]] skips all rows where 3rd column isn't equal to "ABC", nil or 1 */
282
308
  option = rb_hash_aref(options, ID2SYM(rb_intern("only_rows")));
283
309
  if (option != Qnil) {
284
- meta.num_only_rows = (size_t)RARRAY_LEN(option);
285
- meta.only_rows = (char **)malloc(meta.num_only_rows * sizeof(char *));
310
+ meta->num_only_rows = (size_t)RARRAY_LEN(option);
311
+ meta->only_rows = (VALUE *)malloc(meta->num_only_rows * sizeof(VALUE));
286
312
 
287
- for (i = 0; i < meta.num_only_rows; i++) {
313
+ for (i = 0; i < meta->num_only_rows; i++) {
288
314
  VALUE only_row = rb_ary_entry(option, i);
289
- if (only_row == Qnil) {
290
- meta.only_rows[i] = NULL;
291
- } else {
292
- meta.only_rows[i] = StringValueCStr(only_row);
293
- }
315
+ meta->only_rows[i] = validate_filter_row("only_rows", only_row);
316
+ }
317
+ }
318
+
319
+ /* :except_rows is a list of values where row is only parsed
320
+ if its fields don't match those in the passed array.
321
+ [nil, nil, ["ABC", nil, 1]] skips all rows where 3rd column is equal to "ABC", nil or 1 */
322
+ option = rb_hash_aref(options, ID2SYM(rb_intern("except_rows")));
323
+ if (option != Qnil) {
324
+ meta->num_except_rows = (size_t)RARRAY_LEN(option);
325
+ meta->except_rows = (VALUE *)malloc(meta->num_except_rows * sizeof(VALUE));
326
+
327
+ for (i = 0; i < meta->num_except_rows; i++) {
328
+ VALUE except_row = rb_ary_entry(option, i);
329
+ meta->except_rows[i] = validate_filter_row("except_rows", except_row);
294
330
  }
295
331
  }
296
332
 
@@ -298,12 +334,12 @@ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
298
334
  according to matching field positions */
299
335
  option = rb_hash_aref(options, ID2SYM(rb_intern("row_defaults")));
300
336
  if (option != Qnil) {
301
- meta.num_row_defaults = RARRAY_LEN(option);
302
- meta.row_defaults = (VALUE*)malloc(meta.num_row_defaults * sizeof(VALUE*));
337
+ meta->num_row_defaults = RARRAY_LEN(option);
338
+ meta->row_defaults = (VALUE*)malloc(meta->num_row_defaults * sizeof(VALUE*));
303
339
 
304
- for (i = 0; i < meta.num_row_defaults; i++) {
340
+ for (i = 0; i < meta->num_row_defaults; i++) {
305
341
  VALUE row_default = rb_ary_entry(option, i);
306
- meta.row_defaults[i] = row_default;
342
+ meta->row_defaults[i] = row_default;
307
343
  }
308
344
  }
309
345
 
@@ -311,27 +347,27 @@ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
311
347
  Each char of row_conversions string represents Ruby type for CSV field with matching position. */
312
348
  option = rb_hash_aref(options, ID2SYM(rb_intern("row_conversions")));
313
349
  if (option != Qnil) {
314
- meta.num_row_conversions = RSTRING_LEN(option);
315
- meta.row_conversions = StringValuePtr(option);
350
+ meta->num_row_conversions = RSTRING_LEN(option);
351
+ meta->row_conversions = StringValuePtr(option);
316
352
  }
317
353
 
318
354
  /* Column names should be declared explicitly when parsing fields as Hashes */
319
- if (meta.row_as_hash) { /* Only matters for hash results */
355
+ if (meta->row_as_hash) { /* Only matters for hash results */
320
356
  option = rb_hash_aref(options, ID2SYM(rb_intern("column_names")));
321
357
  if (option == Qnil) {
322
358
  rb_raise(rcsv_parse_error, ":row_as_hash requires :column_names to be set.");
323
359
  } else {
324
- meta.last_entry = rb_hash_new();
360
+ meta->last_entry = rb_hash_new();
325
361
 
326
- meta.num_columns = (size_t)RARRAY_LEN(option);
327
- meta.column_names = (VALUE*)malloc(meta.num_columns * sizeof(VALUE*));
362
+ meta->num_columns = (size_t)RARRAY_LEN(option);
363
+ meta->column_names = (VALUE*)malloc(meta->num_columns * sizeof(VALUE*));
328
364
 
329
- for (i = 0; i < meta.num_columns; i++) {
330
- meta.column_names[i] = rb_ary_entry(option, i);
365
+ for (i = 0; i < meta->num_columns; i++) {
366
+ meta->column_names[i] = rb_ary_entry(option, i);
331
367
  }
332
368
  }
333
369
  } else {
334
- meta.last_entry = rb_ary_new();
370
+ meta->last_entry = rb_ary_new();
335
371
  }
336
372
 
337
373
  while(true) {
@@ -342,9 +378,9 @@ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
342
378
  csv_string_len = strlen(csv_string);
343
379
 
344
380
  /* Actual parsing and error handling */
345
- if (csv_string_len != csv_parse(&cp, csv_string, csv_string_len,
346
- &end_of_field_callback, &end_of_line_callback, &meta)) {
347
- error = csv_error(&cp);
381
+ if (csv_string_len != csv_parse(cp, csv_string, csv_string_len,
382
+ &end_of_field_callback, &end_of_line_callback, meta)) {
383
+ error = csv_error(cp);
348
384
  switch(error) {
349
385
  case CSV_EPARSE:
350
386
  rb_raise(rcsv_parse_error, "Error when parsing malformed data");
@@ -364,22 +400,85 @@ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
364
400
  }
365
401
  }
366
402
 
367
- /* Flushing libcsv's buffer and freeing up allocated memory */
368
- csv_fini(&cp, &end_of_field_callback, &end_of_line_callback, &meta);
369
- csv_free(&cp);
403
+ /* Flushing libcsv's buffer */
404
+ csv_fini(cp, &end_of_field_callback, &end_of_line_callback, meta);
405
+
406
+ return Qnil;
407
+ }
408
+
409
+ /* C API */
410
+
411
+ /* The main method that handles parsing */
412
+ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
413
+ struct rcsv_metadata meta;
414
+ VALUE csvio, options, option;
415
+ VALUE ensure_container = rb_ary_new(); /* [] */
416
+
417
+ struct csv_parser cp;
418
+ unsigned char csv_options = CSV_STRICT_FINI | CSV_APPEND_NULL;
419
+
420
+ /* Setting up some sane defaults */
421
+ meta.row_as_hash = false;
422
+ meta.empty_field_is_nil = false;
423
+ meta.skip_current_row = false;
424
+ meta.num_columns = 0;
425
+ meta.current_col = 0;
426
+ meta.current_row = 0;
427
+ meta.offset_rows = 0;
428
+ meta.num_only_rows = 0;
429
+ meta.num_except_rows = 0;
430
+ meta.num_row_defaults = 0;
431
+ meta.num_row_conversions = 0;
432
+ meta.only_rows = NULL;
433
+ meta.except_rows = NULL;
434
+ meta.row_defaults = NULL;
435
+ meta.row_conversions = NULL;
436
+ meta.column_names = NULL;
437
+ meta.result = (VALUE[]){rb_ary_new()}; /* [] */
438
+
439
+ /* csvio is required, options is optional (pun intended) */
440
+ rb_scan_args(argc, argv, "11", &csvio, &options);
441
+
442
+ /* options ||= {} */
443
+ if (NIL_P(options)) {
444
+ options = rb_hash_new();
445
+ }
446
+
447
+ /* First of all, we parse libcsv-related params so that it fails early if something is wrong with them */
370
448
 
371
- if (meta.only_rows != NULL) {
372
- free(meta.only_rows);
449
+ /* By default, parsing is strict */
450
+ option = rb_hash_aref(options, ID2SYM(rb_intern("nostrict")));
451
+ if (!option || (option == Qnil)) {
452
+ csv_options |= CSV_STRICT;
373
453
  }
374
454
 
375
- if (meta.row_defaults != NULL) {
376
- free(meta.row_defaults);
455
+ /* By default, empty strings are treated as Nils and quoted empty strings are treated as empty Ruby strings */
456
+ option = rb_hash_aref(options, ID2SYM(rb_intern("parse_empty_fields_as")));
457
+ if ((option == Qnil) || (option == ID2SYM(rb_intern("nil_or_string")))) {
458
+ csv_options |= CSV_EMPTY_IS_NULL;
459
+ } else if (option == ID2SYM(rb_intern("nil"))) {
460
+ meta.empty_field_is_nil = true;
461
+ } else if (option == ID2SYM(rb_intern("string"))) {
462
+ meta.empty_field_is_nil = false;
463
+ } else {
464
+ rb_raise(rcsv_parse_error, "The only valid options for :parse_empty_fields_as are :nil, :string and :nil_or_string, but %s was supplied.", RSTRING_PTR(rb_inspect(option)));
377
465
  }
378
466
 
379
- if (meta.column_names != NULL) {
380
- free(meta.column_names);
467
+ /* rb_ensure() only expects callback functions to accept and return VALUEs */
468
+ /* This ugly hack converts C pointers into Ruby Fixnums in order to pass them in Array */
469
+ rb_ary_push(ensure_container, options); /* [options] */
470
+ rb_ary_push(ensure_container, csvio); /* [options, csvio] */
471
+ rb_ary_push(ensure_container, LONG2NUM((long)&meta)); /* [options, csvio, &meta] */
472
+ rb_ary_push(ensure_container, LONG2NUM((long)&cp)); /* [options, csvio, &meta, &cp] */
473
+
474
+ /* Try to initialize libcsv */
475
+ if (csv_init(&cp, csv_options) == -1) {
476
+ rb_raise(rcsv_parse_error, "Couldn't initialize libcsv");
381
477
  }
382
478
 
479
+ /* From now on, cp handles allocated data and should be free'd on exit or exception */
480
+ rb_ensure(rcsv_raw_parse, ensure_container, rcsv_free_memory, ensure_container);
481
+
383
482
  /* Remove the last row if it's empty. That happens if CSV file ends with a newline. */
384
483
  if (RARRAY_LEN(*(meta.result)) && /* meta.result.size != 0 */
385
484
  RARRAY_LEN(rb_ary_entry(*(meta.result), -1)) == 0) {
@@ -393,7 +492,6 @@ static VALUE rb_rcsv_raw_parse(int argc, VALUE * argv, VALUE self) {
393
492
  }
394
493
  }
395
494
 
396
-
397
495
  /* Define Ruby API */
398
496
  void Init_rcsv(void) {
399
497
  VALUE klass = rb_define_class("Rcsv", rb_cObject); /* class Rcsv; end */
@@ -59,6 +59,7 @@ class Rcsv
59
59
 
60
60
  if options[:columns]
61
61
  only_rows = []
62
+ except_rows = []
62
63
  row_defaults = []
63
64
  column_names = []
64
65
  row_conversions = ''
@@ -67,10 +68,29 @@ class Rcsv
67
68
  column_options = options[:columns][column_header]
68
69
  if column_options
69
70
  if (options[:row_as_hash])
70
- column_names << column_options[:alias] || column_header
71
+ column_names << (column_options[:alias] || column_header)
71
72
  end
73
+
72
74
  row_defaults << column_options[:default] || nil
73
- only_rows << column_options[:match] || nil
75
+
76
+ only_rows << case column_options[:match]
77
+ when Array
78
+ column_options[:match]
79
+ when nil
80
+ nil
81
+ else
82
+ [column_options[:match]]
83
+ end
84
+
85
+ except_rows << case column_options[:not_match]
86
+ when Array
87
+ column_options[:not_match]
88
+ when nil
89
+ nil
90
+ else
91
+ [column_options[:not_match]]
92
+ end
93
+
74
94
  row_conversions << case column_options[:type]
75
95
  when :int
76
96
  'i'
@@ -83,24 +103,27 @@ class Rcsv
83
103
  when nil
84
104
  's' # strings by default
85
105
  else
86
- fail "Unknown column type"
106
+ fail "Unknown column type #{column_options[:type].inspect}."
87
107
  end
88
108
  elsif options[:only_listed_columns]
89
109
  column_names << nil
90
110
  row_defaults << nil
91
111
  only_rows << nil
112
+ except_rows << nil
92
113
  row_conversions << ' '
93
114
  else
94
115
  column_names << column_header
95
116
  row_defaults << nil
96
117
  only_rows << nil
118
+ except_rows << nil
97
119
  row_conversions << 's'
98
120
  end
99
121
  end
100
122
 
101
123
  raw_options[:column_names] = column_names if options[:row_as_hash]
102
- raw_options[:only_rows] = only_rows
103
- raw_options[:row_defaults] = row_defaults
124
+ raw_options[:only_rows] = only_rows unless only_rows.compact.empty?
125
+ raw_options[:except_rows] = except_rows unless except_rows.compact.empty?
126
+ raw_options[:row_defaults] = row_defaults unless row_defaults.compact.empty?
104
127
  raw_options[:row_conversions] = row_conversions
105
128
  end
106
129
 
@@ -1,3 +1,3 @@
1
1
  class Rcsv
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,4 +1,4 @@
1
- DSAdsfksjh,iii ooo iii,EDADEDADEDADEDADEDADEDAD,111 333 555,NMLKTF,---==---,//,###,0000000000,Asdad bvd qwert,;'''sd,@@@,OCTZ,$$$908080, """",true/false
1
+ DSAdsfksjh,iii ooo iii,EDADEDADEDADEDADEDADEDAD,111 333 555,NMLKTF,---==---,//,###,0000000000,Asdad bvd qwert,;sd,@@@,OCTZ,$$$908080, ,true/false
2
2
  C85A5B9F,85259637,,96,6838,1983-06-14,"""""C4CA-=; **1679; .. 79","210,11",908e,1281-03-09,7257.4654049904275,20efe749-50fe-4b6a-a603-7f9cd1dc6c6d,3,"New York, NY",u,2.228169203286535,t
3
3
  3ABC8DF7,83229030,,79,9134,1987-05-24,"""""8F14-=; **C9F0; .. 74","321,42",b896,1358-04-08,-139.10142026231654,3e18cc6f-8ef0-41ac-bd4c-79a0120f65fd,8,"San Jose, CA",,-9.549296585513721,1
4
4
  F0A83489,69118080,,73,7008,2016-10-03,"""""C81E-=; **ECCB; .. 89","130,86",a3eb,1341-04-10,7612.699237971538,5b5e3fce-2ea5-4ca9-9749-90fd6dc8dd66,9,"Los Angeles, CA",e,6.047887837492023,f
@@ -0,0 +1,79 @@
1
+ require 'test/unit'
2
+ require 'rcsv'
3
+
4
+ class RcsvParseTest < Test::Unit::TestCase
5
+ def setup
6
+ @options = {
7
+ :header => true,
8
+ :columns => {
9
+ 'Date' => {
10
+ :type => :string
11
+ }
12
+ }
13
+ }
14
+ end
15
+
16
+ def test_rcsv_parse_unknown_rows
17
+ csv = "a,b,c,d,e\n1,2,3,4,5"
18
+ parsed_data = Rcsv.parse(csv,
19
+ :row_as_hash => true,
20
+ :columns => {
21
+ 'b' => {
22
+ :type => :int,
23
+ :alias => 'B'
24
+ },
25
+ 'd' => {
26
+ :type => :int
27
+ }
28
+ }
29
+ )
30
+
31
+ assert_equal({
32
+ 'a' => '1',
33
+ 'B' => 2,
34
+ 'c' => '3',
35
+ 'd' => 4,
36
+ 'e' => '5'
37
+ }, parsed_data.first)
38
+ end
39
+
40
+ def test_rcsv_parse_only_rows
41
+ csv = "a,1,t\nb,2,false\nc,3,0"
42
+ parsed_data = Rcsv.parse(csv,
43
+ :header => :none,
44
+ :columns => [
45
+ {
46
+ :match => ['z', 'a', '1']
47
+ },
48
+ {},
49
+ {
50
+ :match => true,
51
+ :type => :bool
52
+ }
53
+ ]
54
+ )
55
+
56
+ assert_equal([["a", "1", true]], parsed_data)
57
+ end
58
+
59
+ def test_rcsv_parse_only_rows_not_match
60
+ csv = "a,1,t\nb,2,false\nc,3,0"
61
+ parsed_data = Rcsv.parse(csv,
62
+ :header => :none,
63
+ :columns => [
64
+ {
65
+ :not_match => ['z', 'a', '1']
66
+ },
67
+ {
68
+ :type => :int
69
+ },
70
+ {
71
+ :not_match => true,
72
+ :type => :bool
73
+ }
74
+ ]
75
+ )
76
+
77
+ assert_equal([["b", 2, false], ["c", 3, false]], parsed_data)
78
+ end
79
+ end
@@ -9,47 +9,47 @@ class RcsvRawParseTest < Test::Unit::TestCase
9
9
  def test_rcsv
10
10
  raw_parsed_csv_data = Rcsv.raw_parse(@csv_data)
11
11
 
12
- assert_equal(raw_parsed_csv_data[0][2], 'EDADEDADEDADEDADEDADEDAD')
13
- assert_equal(raw_parsed_csv_data[0][13], '$$$908080')
14
- assert_equal(raw_parsed_csv_data[0][14], '"')
15
- assert_equal(raw_parsed_csv_data[0][15], 'true/false')
16
- assert_equal(raw_parsed_csv_data[0][16], nil)
17
- assert_equal(raw_parsed_csv_data[9][2], nil)
18
- assert_equal(raw_parsed_csv_data[3][6], '""C81E-=; **ECCB; .. 89')
19
- assert_equal(raw_parsed_csv_data[888][13], 'Dallas, TX')
12
+ assert_equal('EDADEDADEDADEDADEDADEDAD', raw_parsed_csv_data[0][2])
13
+ assert_equal('$$$908080', raw_parsed_csv_data[0][13])
14
+ assert_equal('"', raw_parsed_csv_data[0][14])
15
+ assert_equal('true/false', raw_parsed_csv_data[0][15])
16
+ assert_equal(nil, raw_parsed_csv_data[0][16])
17
+ assert_equal(nil, raw_parsed_csv_data[9][2])
18
+ assert_equal('""C81E-=; **ECCB; .. 89', raw_parsed_csv_data[3][6])
19
+ assert_equal('Dallas, TX', raw_parsed_csv_data[888][13])
20
20
  end
21
21
 
22
22
  def test_rcsv_col_sep
23
23
  tsv_data = StringIO.new(@csv_data.read.tr(",", "\t"))
24
24
  raw_parsed_tsv_data = Rcsv.raw_parse(tsv_data, :col_sep => "\t")
25
25
 
26
- assert_equal(raw_parsed_tsv_data[0][2], 'EDADEDADEDADEDADEDADEDAD')
27
- assert_equal(raw_parsed_tsv_data[0][13], '$$$908080')
28
- assert_equal(raw_parsed_tsv_data[0][14], '"')
29
- assert_equal(raw_parsed_tsv_data[0][15], 'true/false')
30
- assert_equal(raw_parsed_tsv_data[0][16], nil)
31
- assert_equal(raw_parsed_tsv_data[9][2], nil)
32
- assert_equal(raw_parsed_tsv_data[3][6], '""C81E-=; **ECCB; .. 89')
33
- assert_equal(raw_parsed_tsv_data[888][13], "Dallas\t TX")
26
+ assert_equal('EDADEDADEDADEDADEDADEDAD', raw_parsed_tsv_data[0][2])
27
+ assert_equal('$$$908080', raw_parsed_tsv_data[0][13])
28
+ assert_equal('"', raw_parsed_tsv_data[0][14])
29
+ assert_equal('true/false', raw_parsed_tsv_data[0][15])
30
+ assert_equal(nil, raw_parsed_tsv_data[0][16])
31
+ assert_equal(nil, raw_parsed_tsv_data[9][2])
32
+ assert_equal('""C81E-=; **ECCB; .. 89', raw_parsed_tsv_data[3][6])
33
+ assert_equal("Dallas\t TX", raw_parsed_tsv_data[888][13])
34
34
  end
35
35
 
36
36
  def test_buffer_size
37
37
  raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :buffer_size => 10)
38
38
 
39
- assert_equal(raw_parsed_csv_data[0][2], 'EDADEDADEDADEDADEDADEDAD')
40
- assert_equal(raw_parsed_csv_data[0][13], '$$$908080')
41
- assert_equal(raw_parsed_csv_data[0][14], '"')
42
- assert_equal(raw_parsed_csv_data[0][15], 'true/false')
43
- assert_equal(raw_parsed_csv_data[0][16], nil)
44
- assert_equal(raw_parsed_csv_data[9][2], nil)
45
- assert_equal(raw_parsed_csv_data[3][6], '""C81E-=; **ECCB; .. 89')
46
- assert_equal(raw_parsed_csv_data[888][13], 'Dallas, TX')
39
+ assert_equal('EDADEDADEDADEDADEDADEDAD', raw_parsed_csv_data[0][2])
40
+ assert_equal('$$$908080', raw_parsed_csv_data[0][13])
41
+ assert_equal('"', raw_parsed_csv_data[0][14])
42
+ assert_equal('true/false', raw_parsed_csv_data[0][15])
43
+ assert_equal(nil, raw_parsed_csv_data[0][16])
44
+ assert_equal(nil, raw_parsed_csv_data[9][2])
45
+ assert_equal('""C81E-=; **ECCB; .. 89', raw_parsed_csv_data[3][6])
46
+ assert_equal('Dallas, TX', raw_parsed_csv_data[888][13])
47
47
  end
48
48
 
49
49
  def test_single_item_csv
50
50
  raw_parsed_csv_data = Rcsv.raw_parse(StringIO.new("Foo"))
51
51
 
52
- assert_equal(raw_parsed_csv_data, [["Foo"]])
52
+ assert_equal([["Foo"]], raw_parsed_csv_data)
53
53
  end
54
54
 
55
55
  def test_broken_data
@@ -68,60 +68,68 @@ class RcsvRawParseTest < Test::Unit::TestCase
68
68
  end
69
69
 
70
70
  def test_only_rows
71
- raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => ['GBP'])
71
+ raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => [['GBP', 'NO SUCH THING']])
72
72
 
73
- assert_equal(raw_parsed_csv_data[0][0], 'GBP')
74
- assert_equal(raw_parsed_csv_data[-1][3], '15')
75
- assert_equal(raw_parsed_csv_data.size, 3)
73
+ assert_equal('GBP', raw_parsed_csv_data[0][0])
74
+ assert_equal('15', raw_parsed_csv_data[-1][3])
75
+ assert_equal(3, raw_parsed_csv_data.size)
76
+ end
77
+
78
+ def test_except_rows
79
+ raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :except_rows => [['GBP', 'NO SUCH THING']])
80
+
81
+ assert_equal('C85A5B9F', raw_parsed_csv_data[1][0])
82
+ assert_equal('4971', raw_parsed_csv_data[28][4])
83
+ assert_equal(886, raw_parsed_csv_data.size)
76
84
  end
77
85
 
78
86
  def test_only_rows_with_nil_and_empty_string_filter
79
- raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => ['GBP', nil, '', '51'])
87
+ raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => [['GBP'], nil, ['zzz', nil], ['51']])
80
88
 
81
- assert_equal(raw_parsed_csv_data[0][0], 'GBP')
82
- assert_equal(raw_parsed_csv_data[0][2], nil)
83
- assert_equal(raw_parsed_csv_data.size, 1)
89
+ assert_equal('GBP', raw_parsed_csv_data[0][0])
90
+ assert_equal(nil, raw_parsed_csv_data[0][2])
91
+ assert_equal(1, raw_parsed_csv_data.size)
84
92
  end
85
93
 
86
94
  def test_only_rows_with_nil_beginning
87
- raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => [nil, nil, nil, '96', nil])
95
+ raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :only_rows => [nil, nil, nil, [123, 96], nil], :row_conversions => 'sssi')
88
96
 
89
- assert_equal(raw_parsed_csv_data[1][0], 'C3B87A6B')
90
- assert_equal(raw_parsed_csv_data[0][2], nil)
91
- assert_equal(raw_parsed_csv_data[3][3], '96')
92
- assert_equal(raw_parsed_csv_data.size, 5)
97
+ assert_equal('C3B87A6B', raw_parsed_csv_data[1][0])
98
+ assert_equal(nil, raw_parsed_csv_data[0][2])
99
+ assert_equal(96, raw_parsed_csv_data[3][3])
100
+ assert_equal(5, raw_parsed_csv_data.size)
93
101
  end
94
102
 
95
103
  def test_row_defaults
96
104
  raw_parsed_csv_data = Rcsv.raw_parse(@csv_data, :row_defaults => [nil, nil, :booya, nil, 'never ever'])
97
105
 
98
- assert_equal(raw_parsed_csv_data[0][2], 'EDADEDADEDADEDADEDADEDAD')
99
- assert_equal(raw_parsed_csv_data[1][2], :booya)
100
- assert_equal(raw_parsed_csv_data[1][0], 'C85A5B9F')
101
- assert_equal(raw_parsed_csv_data[2][4], '9134')
106
+ assert_equal('EDADEDADEDADEDADEDADEDAD', raw_parsed_csv_data[0][2])
107
+ assert_equal(:booya, raw_parsed_csv_data[1][2])
108
+ assert_equal('C85A5B9F', raw_parsed_csv_data[1][0])
109
+ assert_equal('9134', raw_parsed_csv_data[2][4])
102
110
  end
103
111
 
104
112
  def test_row_conversions
105
113
  raw_parsed_csv_data = Rcsv.raw_parse(StringIO.new(@csv_data.each_line.to_a[1..-1].join), # skipping string headers
106
114
  :row_conversions => 'sisiisssssfsissf')
107
115
 
108
- assert_equal(raw_parsed_csv_data[0][2], nil)
109
- assert_equal(raw_parsed_csv_data[0][3], 96)
110
- assert_equal(raw_parsed_csv_data[0][8], '908e')
111
- assert_equal(raw_parsed_csv_data[1][15], -9.549296585513721)
112
- assert_equal(raw_parsed_csv_data[3][5], '2015-12-22')
116
+ assert_equal(nil, raw_parsed_csv_data[0][2])
117
+ assert_equal(96, raw_parsed_csv_data[0][3])
118
+ assert_equal('908e', raw_parsed_csv_data[0][8])
119
+ assert_equal(-9.549296585513721, raw_parsed_csv_data[1][15])
120
+ assert_equal('2015-12-22', raw_parsed_csv_data[3][5])
113
121
  end
114
122
 
115
123
  def test_row_conversions_with_column_exclusions
116
124
  raw_parsed_csv_data = Rcsv.raw_parse(StringIO.new(@csv_data.each_line.to_a[1..-1].join), # skipping string headers
117
125
  :row_conversions => 's f issss fsis fb')
118
126
 
119
- assert_equal(raw_parsed_csv_data[0][1], nil)
120
- assert_equal(raw_parsed_csv_data[0][2], 6838)
121
- assert_equal(raw_parsed_csv_data[0][8], '20efe749-50fe-4b6a-a603-7f9cd1dc6c6d')
122
- assert_equal(raw_parsed_csv_data[0][12], true)
123
- assert_equal(raw_parsed_csv_data[0][13], nil)
124
- assert_equal(raw_parsed_csv_data[4][3], '2020-12-09')
127
+ assert_equal(nil, raw_parsed_csv_data[0][1])
128
+ assert_equal(6838, raw_parsed_csv_data[0][2])
129
+ assert_equal('20efe749-50fe-4b6a-a603-7f9cd1dc6c6d', raw_parsed_csv_data[0][8])
130
+ assert_equal(true, raw_parsed_csv_data[0][12])
131
+ assert_equal(nil, raw_parsed_csv_data[0][13])
132
+ assert_equal('2020-12-09', raw_parsed_csv_data[4][3])
125
133
  end
126
134
 
127
135
  def test_offset_rows
@@ -181,14 +189,14 @@ class RcsvRawParseTest < Test::Unit::TestCase
181
189
  }
182
190
 
183
191
  assert_equal(nil, result)
184
- assert_equal(raw_parsed_csv_data[0][2], 'EDADEDADEDADEDADEDADEDAD')
185
- assert_equal(raw_parsed_csv_data[0][13], '$$$908080')
186
- assert_equal(raw_parsed_csv_data[0][14], '"')
187
- assert_equal(raw_parsed_csv_data[0][15], 'true/false')
188
- assert_equal(raw_parsed_csv_data[0][16], nil)
189
- assert_equal(raw_parsed_csv_data[9][2], nil)
190
- assert_equal(raw_parsed_csv_data[3][6], '""C81E-=; **ECCB; .. 89')
191
- assert_equal(raw_parsed_csv_data[888][13], 'Dallas, TX')
192
+ assert_equal('EDADEDADEDADEDADEDADEDAD', raw_parsed_csv_data[0][2])
193
+ assert_equal('$$$908080', raw_parsed_csv_data[0][13])
194
+ assert_equal('"', raw_parsed_csv_data[0][14])
195
+ assert_equal('true/false', raw_parsed_csv_data[0][15])
196
+ assert_equal(nil, raw_parsed_csv_data[0][16])
197
+ assert_equal(nil, raw_parsed_csv_data[9][2])
198
+ assert_equal('""C81E-=; **ECCB; .. 89', raw_parsed_csv_data[3][6])
199
+ assert_equal('Dallas, TX', raw_parsed_csv_data[888][13])
192
200
  end
193
201
 
194
202
  def test_hash_block_streaming
@@ -1,5 +1,6 @@
1
1
  require 'test/unit'
2
2
  require 'rcsv'
3
+ require 'date'
3
4
 
4
5
  class RcsvWriteTest < Test::Unit::TestCase
5
6
  def setup
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rcsv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
5
- prerelease:
4
+ version: 0.1.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Arthur Pirogovski
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-22 00:00:00.000000000 Z
11
+ date: 2013-08-18 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: A libcsv-based CSV parser for Ruby
15
14
  email:
@@ -39,36 +38,36 @@ files:
39
38
  - lib/rcsv/version.rb
40
39
  - rcsv.gemspec
41
40
  - test/test_rcsv.csv
41
+ - test/test_rcsv_parse.rb
42
42
  - test/test_rcsv_raw_parse.rb
43
43
  - test/test_rcsv_write.rb
44
44
  homepage: http://github.com/fiksu/rcsv
45
45
  licenses: []
46
+ metadata: {}
46
47
  post_install_message:
47
48
  rdoc_options: []
48
49
  require_paths:
49
50
  - lib
50
51
  - ext
51
52
  required_ruby_version: !ruby/object:Gem::Requirement
52
- none: false
53
53
  requirements:
54
54
  - - ! '>='
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
57
  required_rubygems_version: !ruby/object:Gem::Requirement
58
- none: false
59
58
  requirements:
60
59
  - - ! '>='
61
60
  - !ruby/object:Gem::Version
62
61
  version: '0'
63
62
  requirements: []
64
63
  rubyforge_project:
65
- rubygems_version: 1.8.24
64
+ rubygems_version: 2.0.3
66
65
  signing_key:
67
- specification_version: 3
66
+ specification_version: 4
68
67
  summary: Fast CSV parsing library for MRI based on libcsv. Supports type conversion,
69
68
  non-strict parsing and basic filtering.
70
69
  test_files:
71
70
  - test/test_rcsv.csv
71
+ - test/test_rcsv_parse.rb
72
72
  - test/test_rcsv_raw_parse.rb
73
73
  - test/test_rcsv_write.rb
74
- has_rdoc: