rcsv 0.0.9 → 0.1.1

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.
@@ -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: