fastcsv 0.0.2 → 0.0.3

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.
@@ -5,7 +5,7 @@
5
5
  // http://w3c.github.io/csvw/syntax/#ebnf
6
6
 
7
7
  // CSV implementation.
8
- // https://github.com/ruby/ruby/blob/master/lib/csv.rb
8
+ // https://github.com/ruby/ruby/blob/trunk/lib/csv.rb
9
9
 
10
10
  // Ruby C extensions help.
11
11
  // https://github.com/ruby/ruby/blob/trunk/README.EXT
@@ -19,15 +19,24 @@ if (enc2 != NULL) { \
19
19
  field = rb_str_encode(field, rb_enc_from_encoding(enc), 0, Qnil); \
20
20
  }
21
21
 
22
- static VALUE mModule, rb_eParseError;
23
- static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string, s_encoding;
22
+ #define FREE \
23
+ if (buf != NULL) { \
24
+ free(buf); \
25
+ } \
26
+ if (row_sep != NULL) { \
27
+ free(row_sep); \
28
+ }
24
29
 
25
- %%{
26
- machine fastcsv;
30
+ static VALUE cClass, cParser, eError;
31
+ static ID s_read, s_row;
27
32
 
28
- action new_line {
29
- curline++;
30
- }
33
+ // @see https://github.com/nofxx/georuby_c/blob/b3b91fd90980d7c295ac8f6012d89878ea7cd569/ext/types.h#L22
34
+ typedef struct {
35
+ char *start;
36
+ } Data;
37
+
38
+ %%{
39
+ machine raw_parse;
31
40
 
32
41
  action open_quote {
33
42
  unclosed_line = curline;
@@ -74,7 +83,7 @@ static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string,
74
83
  reader++;
75
84
  }
76
85
 
77
- field = rb_enc_str_new(copy, writer - copy, enc);
86
+ field = rb_enc_str_new(copy, writer - copy, encoding);
78
87
  ENCODE;
79
88
 
80
89
  if (copy != NULL) {
@@ -88,7 +97,35 @@ static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string,
88
97
  field = Qnil;
89
98
  }
90
99
 
100
+ action mark_row {
101
+ d->start = p;
102
+
103
+ if (len_row_sep) {
104
+ if (p - mark_row_sep != len_row_sep || row_sep[0] != *mark_row_sep || len_row_sep == 2 && row_sep[1] != *(mark_row_sep + 1)) {
105
+ FREE;
106
+
107
+ rb_raise(eError, "Unquoted fields do not allow \\r or \\n (line %d).", curline - 1);
108
+ }
109
+ }
110
+ else {
111
+ len_row_sep = p - mark_row_sep;
112
+ row_sep = ALLOC_N(char, p - mark_row_sep);
113
+ memcpy(row_sep, mark_row_sep, p - mark_row_sep);
114
+ }
115
+ }
116
+
91
117
  action new_row {
118
+ mark_row_sep = p;
119
+
120
+ curline++;
121
+
122
+ if (d->start == 0 || p == d->start) {
123
+ rb_ivar_set(self, s_row, rb_str_new2(""));
124
+ }
125
+ else if (p > d->start) {
126
+ rb_ivar_set(self, s_row, rb_str_new(d->start, p - d->start));
127
+ }
128
+
92
129
  if (!NIL_P(field) || RARRAY_LEN(row)) { // same as new_field
93
130
  rb_ary_push(row, field);
94
131
  field = Qnil;
@@ -99,18 +136,26 @@ static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string,
99
136
  }
100
137
 
101
138
  action last_row {
139
+ if (d->start == 0 || p == d->start) {
140
+ rb_ivar_set(self, s_row, rb_str_new2(""));
141
+ }
142
+ else if (p > d->start) {
143
+ rb_ivar_set(self, s_row, rb_str_new(d->start, p - d->start));
144
+ }
145
+
102
146
  if (!NIL_P(field) || RARRAY_LEN(row)) {
103
147
  rb_ary_push(row, field);
104
148
  }
149
+
105
150
  if (RARRAY_LEN(row)) {
106
151
  rb_yield(row);
107
152
  }
108
153
  }
109
154
 
110
- EOF = 0 >last_row;
155
+ EOF = 0;
111
156
  quote_char = '"';
112
157
  col_sep = ',' >new_field;
113
- row_sep = ('\r' '\n'? | '\n') @new_line;
158
+ row_sep = ('\r' '\n'? | '\n');
114
159
  unquoted = (any* -- quote_char -- col_sep -- row_sep - EOF) %read_unquoted;
115
160
  quoted = quote_char >open_quote (any - quote_char - EOF | quote_char quote_char | row_sep)* %read_quoted quote_char >close_quote;
116
161
  field = unquoted | quoted;
@@ -118,9 +163,9 @@ static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string,
118
163
  # @see Ragel Guide: 6.3 Scanners
119
164
  # An unquoted field can be zero-length.
120
165
  main := |*
121
- field col_sep EOF?;
122
- field row_sep >new_row EOF?;
123
- field EOF;
166
+ field col_sep;
167
+ field row_sep >new_row %mark_row;
168
+ field EOF >last_row;
124
169
  *|;
125
170
  }%%
126
171
 
@@ -130,9 +175,7 @@ static ID s_read, s_to_str, s_internal_encoding, s_external_encoding, s_string,
130
175
  #define BUFSIZE 16384
131
176
 
132
177
  // @see http://rxr.whitequark.org/mri/source/io.c#4845
133
- static void
134
- rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc, rb_encoding **enc2, int fmode)
135
- {
178
+ static void rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc, rb_encoding **enc2, int fmode) {
136
179
  int default_ext = 0;
137
180
 
138
181
  if (ext == NULL) {
@@ -157,15 +200,17 @@ rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc,
157
200
  }
158
201
  }
159
202
 
160
- VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
203
+ static VALUE raw_parse(int argc, VALUE *argv, VALUE self) {
161
204
  int cs, act, have = 0, curline = 1, io = 0;
162
- char *ts = 0, *te = 0, *buf = 0, *eof = 0;
205
+ char *ts = 0, *te = 0, *buf = 0, *eof = 0, *mark_row_sep = 0, *row_sep = NULL;
163
206
 
164
- VALUE port, opts;
207
+ VALUE port, opts, r_encoding;
165
208
  VALUE row = rb_ary_new(), field = Qnil, bufsize = Qnil;
166
- int done = 0, unclosed_line = 0, buffer_size = 0, taint = 0;
209
+ int done = 0, unclosed_line = 0, len_row_sep = 0, buffer_size = 0, taint = 0;
167
210
  rb_encoding *enc = NULL, *enc2 = NULL, *encoding = NULL;
168
- VALUE r_encoding;
211
+
212
+ Data *d;
213
+ Data_Get_Struct(self, Data, d);
169
214
 
170
215
  VALUE option;
171
216
  char quote_char = '"';
@@ -174,8 +219,8 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
174
219
  taint = OBJ_TAINTED(port);
175
220
  io = rb_respond_to(port, s_read);
176
221
  if (!io) {
177
- if (rb_respond_to(port, s_to_str)) {
178
- port = rb_funcall(port, s_to_str, 0);
222
+ if (rb_respond_to(port, rb_intern("to_str"))) {
223
+ port = rb_funcall(port, rb_intern("to_str"), 0);
179
224
  StringValue(port);
180
225
  }
181
226
  else {
@@ -199,7 +244,7 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
199
244
  // @see http://ruby-doc.org/core-2.1.1/IO.html#method-c-new-label-Open+Mode
200
245
  option = rb_hash_aref(opts, ID2SYM(rb_intern("encoding")));
201
246
  if (TYPE(option) == T_STRING) {
202
- // parse_mode_enc is not in header file.
247
+ // `parse_mode_enc` is not in header file.
203
248
  const char *estr = StringValueCStr(option), *ptr;
204
249
  char encname[ENCODING_MAXNAMELEN+1];
205
250
  int idx, idx2;
@@ -210,17 +255,17 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
210
255
  ptr = strrchr(estr, ':');
211
256
  if (ptr) {
212
257
  long len = (ptr++) - estr;
213
- if (len == 0 || len > ENCODING_MAXNAMELEN) {
258
+ if (len == 0 || len > ENCODING_MAXNAMELEN) { // ":enc"
214
259
  idx = -1;
215
260
  }
216
- else {
261
+ else { // "enc2:enc" or "enc:-"
217
262
  memcpy(encname, estr, len);
218
263
  encname[len] = '\0';
219
264
  estr = encname;
220
265
  idx = rb_enc_find_index(encname);
221
266
  }
222
267
  }
223
- else {
268
+ else { // "enc"
224
269
  idx = rb_enc_find_index(estr);
225
270
  }
226
271
 
@@ -228,7 +273,7 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
228
273
  ext_enc = rb_enc_from_index(idx);
229
274
  }
230
275
  else {
231
- if (idx != -2) {
276
+ if (idx != -2) { // ":enc"
232
277
  // `unsupported_encoding` is not in header file.
233
278
  rb_warn("Unsupported encoding %s ignored", estr);
234
279
  }
@@ -237,11 +282,11 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
237
282
 
238
283
  int_enc = NULL;
239
284
  if (ptr) {
240
- if (*ptr == '-' && *(ptr+1) == '\0') {
285
+ if (*ptr == '-' && *(ptr+1) == '\0') { // "enc:-"
241
286
  /* Special case - "-" => no transcoding */
242
287
  int_enc = (rb_encoding *)Qnil;
243
288
  }
244
- else {
289
+ else { // "enc2:enc"
245
290
  idx2 = rb_enc_find_index(ptr);
246
291
  if (idx2 < 0) {
247
292
  // `unsupported_encoding` is not in header file.
@@ -262,29 +307,33 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
262
307
  rb_raise(rb_eArgError, ":encoding has to be a String");
263
308
  }
264
309
 
265
- // @see https://github.com/ruby/ruby/blob/70510d026f8d86693dccaba07417488eed09b41d/lib/csv.rb#L1567
266
- // @see https://github.com/ruby/ruby/blob/70510d026f8d86693dccaba07417488eed09b41d/lib/csv.rb#L2300
267
- if (rb_respond_to(port, s_internal_encoding)) {
268
- r_encoding = rb_funcall(port, s_internal_encoding, 0);
310
+ // @see CSV#raw_encoding
311
+ // @see https://github.com/ruby/ruby/blob/ab337e61ecb5f42384ba7d710c36faf96a454e5c/lib/csv.rb#L2290
312
+ if (rb_respond_to(port, rb_intern("internal_encoding"))) {
313
+ r_encoding = rb_funcall(port, rb_intern("internal_encoding"), 0);
269
314
  if (NIL_P(r_encoding)) {
270
- r_encoding = rb_funcall(port, s_external_encoding, 0);
315
+ r_encoding = rb_funcall(port, rb_intern("external_encoding"), 0);
271
316
  }
272
317
  }
273
- else if (rb_respond_to(port, s_string)) {
274
- r_encoding = rb_funcall(rb_funcall(port, s_string, 0), s_encoding, 0);
318
+ else if (rb_respond_to(port, rb_intern("string"))) {
319
+ r_encoding = rb_funcall(rb_funcall(port, rb_intern("string"), 0), rb_intern("encoding"), 0);
275
320
  }
276
- else if (rb_respond_to(port, s_encoding)) {
277
- r_encoding = rb_funcall(port, s_encoding, 0);
321
+ else if (rb_respond_to(port, rb_intern("encoding"))) {
322
+ r_encoding = rb_funcall(port, rb_intern("encoding"), 0);
278
323
  }
279
324
  else {
280
325
  r_encoding = rb_enc_from_encoding(rb_ascii8bit_encoding());
281
326
  }
327
+
328
+ // @see CSV#initialize
329
+ // @see https://github.com/ruby/ruby/blob/ab337e61ecb5f42384ba7d710c36faf96a454e5c/lib/csv.rb#L1510
282
330
  if (NIL_P(r_encoding)) {
283
331
  r_encoding = rb_enc_from_encoding(rb_default_internal_encoding());
284
332
  }
285
333
  if (NIL_P(r_encoding)) {
286
334
  r_encoding = rb_enc_from_encoding(rb_default_external_encoding());
287
335
  }
336
+
288
337
  if (enc2 != NULL) {
289
338
  encoding = enc2;
290
339
  }
@@ -295,11 +344,19 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
295
344
  encoding = rb_enc_get(r_encoding);
296
345
  }
297
346
 
347
+ // In case #raw_parse is called multiple times on the same parser. Note that
348
+ // using IO methods on a re-used parser can cause segmentation faults.
349
+ rb_ivar_set(self, s_row, Qnil);
350
+
298
351
  buffer_size = BUFSIZE;
299
352
  if (rb_ivar_defined(self, rb_intern("@buffer_size")) == Qtrue) {
300
353
  bufsize = rb_ivar_get(self, rb_intern("@buffer_size"));
301
354
  if (!NIL_P(bufsize)) {
302
355
  buffer_size = NUM2INT(bufsize);
356
+ // buffer_size = 0 can cause segmentation faults.
357
+ if (buffer_size == 0) {
358
+ buffer_size = BUFSIZE;
359
+ }
303
360
  }
304
361
  }
305
362
 
@@ -312,26 +369,34 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
312
369
  while (!done) {
313
370
  VALUE str;
314
371
  char *p, *pe;
315
- int len, space = buffer_size - have, tokstart_diff, tokend_diff;
372
+ int len, space = buffer_size - have, tokstart_diff, tokend_diff, start_diff, mark_row_sep_diff;
316
373
 
317
374
  if (io) {
318
375
  if (space == 0) {
319
- tokstart_diff = ts - buf;
320
- tokend_diff = te - buf;
376
+ // Not moving d->start will cause intermittent segmentation faults.
377
+ tokstart_diff = ts - buf;
378
+ tokend_diff = te - buf;
379
+ start_diff = d->start - buf;
380
+ mark_row_sep_diff = mark_row_sep - buf;
321
381
 
322
- buffer_size += BUFSIZE;
323
- REALLOC_N(buf, char, buffer_size);
382
+ buffer_size += BUFSIZE;
383
+ REALLOC_N(buf, char, buffer_size);
324
384
 
325
- space = buffer_size - have;
385
+ space = buffer_size - have;
326
386
 
327
- ts = buf + tokstart_diff;
328
- te = buf + tokend_diff;
387
+ ts = buf + tokstart_diff;
388
+ te = buf + tokend_diff;
389
+ d->start = buf + start_diff;
390
+ mark_row_sep = buf + mark_row_sep_diff;
329
391
  }
330
392
  p = buf + have;
331
393
 
394
+ // Reads "`length` bytes without any conversion (binary mode)."
395
+ // "The resulted string is always ASCII-8BIT encoding."
396
+ // @see http://www.ruby-doc.org/core-2.1.4/IO.html#method-i-read
332
397
  str = rb_funcall(port, s_read, 1, INT2FIX(space));
333
398
  if (NIL_P(str)) {
334
- // StringIO#read returns nil for empty string.
399
+ // "`nil` means it met EOF at beginning," e.g. for `StringIO.new("")`.
335
400
  len = 0;
336
401
  }
337
402
  else {
@@ -339,6 +404,7 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
339
404
  memcpy(p, StringValuePtr(str), len);
340
405
  }
341
406
 
407
+ // "The 1 to `length`-1 bytes string means it met EOF after reading the result."
342
408
  if (len < space) {
343
409
  // EOF actions don't work in scanners, so we add a sentinel value.
344
410
  // @see http://www.complang.org/pipermail/ragel-users/2007-May/001516.html
@@ -354,22 +420,21 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
354
420
  done = 1;
355
421
  }
356
422
 
423
+ if (d->start == 0) {
424
+ d->start = p;
425
+ }
426
+
357
427
  pe = p + len;
358
428
  %% write exec;
359
429
 
360
- if (done && cs < fastcsv_first_final) {
361
- if (buf != NULL) {
362
- free(buf);
363
- }
430
+ if (done && cs < raw_parse_first_final) {
431
+ FREE;
432
+
364
433
  if (unclosed_line) {
365
- rb_raise(rb_eParseError, "Unclosed quoted field on line %d.", unclosed_line);
434
+ rb_raise(eError, "Unclosed quoted field on line %d.", unclosed_line);
366
435
  }
367
- // Ruby raises different errors for illegal quoting, depending on whether
368
- // a quoted string is followed by a string ("Unclosed quoted field on line
369
- // %d.") or by a string ending in a quote ("Missing or stray quote in line
370
- // %d"). These precisions are kind of bogus, but we can try using $!.
371
436
  else {
372
- rb_raise(rb_eParseError, "Illegal quoting in line %d.", curline);
437
+ rb_raise(eError, "Illegal quoting in line %d.", curline);
373
438
  }
374
439
  }
375
440
 
@@ -384,23 +449,35 @@ VALUE fastcsv(int argc, VALUE *argv, VALUE self) {
384
449
  }
385
450
  }
386
451
 
387
- if (buf != NULL) {
388
- free(buf);
389
- }
452
+ FREE;
390
453
 
391
454
  return Qnil;
392
455
  }
393
456
 
457
+ // @see https://github.com/ruby/ruby/blob/trunk/README.EXT#L616
458
+ static VALUE allocate(VALUE class) {
459
+ // @see https://github.com/nofxx/georuby_c/blob/b3b91fd90980d7c295ac8f6012d89878ea7cd569/ext/line.c#L66
460
+ Data *d = ALLOC(Data);
461
+ d->start = 0;
462
+ // @see https://github.com/nofxx/georuby_c/blob/b3b91fd90980d7c295ac8f6012d89878ea7cd569/ext/point.h#L26
463
+ // rb_gc_mark(d->start) or rb_gc_mark(d) cause warning "passing argument 1 of ‘rb_gc_mark’ makes integer from pointer without a cast"
464
+ // free(d->start) causes error "pointer being freed was not allocated"
465
+ return Data_Wrap_Struct(class, NULL, free, d);
466
+ }
467
+
468
+ // @see http://tenderlovemaking.com/2009/12/18/writing-ruby-c-extensions-part-1.html
469
+ // @see http://tenderlovemaking.com/2010/12/11/writing-ruby-c-extensions-part-2.html
394
470
  void Init_fastcsv() {
395
471
  s_read = rb_intern("read");
396
- s_to_str = rb_intern("to_str");
397
- s_internal_encoding = rb_intern("internal_encoding");
398
- s_external_encoding = rb_intern("external_encoding");
399
- s_string = rb_intern("string");
400
- s_encoding = rb_intern("encoding");
401
-
402
- mModule = rb_define_module("FastCSV");
403
- rb_define_attr(rb_singleton_class(mModule), "buffer_size", 1, 1);
404
- rb_define_singleton_method(mModule, "raw_parse", fastcsv, -1);
405
- rb_eParseError = rb_define_class_under(mModule, "ParseError", rb_eStandardError);
472
+ s_row = rb_intern("@row");
473
+
474
+ cClass = rb_define_class("FastCSV", rb_const_get(rb_cObject, rb_intern("CSV"))); // class FastCSV < CSV
475
+ cParser = rb_define_class_under(cClass, "Parser", rb_cObject); // class Parser
476
+ rb_define_alloc_func(cParser, allocate); //
477
+ rb_define_method(cParser, "raw_parse", raw_parse, -1); // def raw_parse(port, opts = nil); end
478
+ rb_define_attr(cParser, "row", 1, 0); // attr_reader :row
479
+ rb_define_attr(cParser, "buffer_size", 1, 1); // attr_accessor :buffer_size
480
+ // end
481
+ eError = rb_define_class_under(cClass, "MalformedCSVError", rb_eRuntimeError); // class MalformedCSVError < RuntimeError
482
+ // end
406
483
  }
data/fastcsv.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "fastcsv"
5
- s.version = '0.0.2'
5
+ s.version = '0.0.3'
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ["Open North"]
8
8
  s.email = ["info@opennorth.ca"]
data/lib/fastcsv.rb CHANGED
@@ -1 +1,131 @@
1
+ require 'csv'
2
+
1
3
  require 'fastcsv/fastcsv'
4
+
5
+ # @see https://github.com/ruby/ruby/blob/ab337e61ecb5f42384ba7d710c36faf96a454e5c/lib/csv.rb
6
+ class FastCSV < CSV
7
+ def self.raw_parse(*args, &block)
8
+ Parser.new.raw_parse(*args, &block)
9
+ end
10
+
11
+ def shift
12
+ # COPY
13
+ # handle headers not based on document content
14
+ if header_row? and @return_headers and
15
+ [Array, String].include? @use_headers.class
16
+ if @unconverted_fields
17
+ return add_unconverted_fields(parse_headers, Array.new)
18
+ else
19
+ return parse_headers
20
+ end
21
+ end
22
+ # PASTE
23
+
24
+ # The CSV library wraps File objects, whereas `FastCSV.raw_parse` accepts
25
+ # IO-like objects that implement `#read(length)`.
26
+ begin
27
+ unless csv = fiber.resume # was unless parse = @io.gets(@row_sep)
28
+ return nil
29
+ end
30
+ rescue FiberError
31
+ return nil
32
+ end
33
+
34
+ row = parser.row
35
+
36
+ # COPY
37
+ if csv.empty?
38
+ #
39
+ # I believe a blank line should be an <tt>Array.new</tt>, not Ruby 1.8
40
+ # CSV's <tt>[nil]</tt>
41
+ #
42
+ if row.empty? # was if parse.empty?
43
+ @lineno += 1
44
+ if @skip_blanks
45
+ return shift # was next
46
+ elsif @unconverted_fields
47
+ return add_unconverted_fields(Array.new, Array.new)
48
+ elsif @use_headers
49
+ return self.class::Row.new(Array.new, Array.new)
50
+ else
51
+ return Array.new
52
+ end
53
+ end
54
+ end
55
+ # PASTE
56
+
57
+ return shift if @skip_lines and @skip_lines.match row # was next if @skip_lines and @skip_lines.match parse
58
+
59
+ # COPY
60
+ @lineno += 1
61
+
62
+ # save fields unconverted fields, if needed...
63
+ unconverted = csv.dup if @unconverted_fields
64
+
65
+ # convert fields, if needed...
66
+ csv = convert_fields(csv) unless @use_headers or @converters.empty?
67
+ # parse out header rows and handle CSV::Row conversions...
68
+ csv = parse_headers(csv) if @use_headers
69
+
70
+ # inject unconverted fields and accessor, if requested...
71
+ if @unconverted_fields and not csv.respond_to? :unconverted_fields
72
+ add_unconverted_fields(csv, unconverted)
73
+ end
74
+ # PASTE
75
+
76
+ csv # was break csv
77
+ end
78
+
79
+ # CSV's delegated and overwritten IO methods move the pointer within the file,
80
+ # but FastCSV doesn't notice, so we need to recreate the fiber. The old fiber
81
+ # is garbage collected.
82
+
83
+ def pos=(*args)
84
+ super
85
+ @parser = nil
86
+ @fiber = nil
87
+ end
88
+ def reopen(*args)
89
+ super
90
+ @parser = nil
91
+ @fiber = nil
92
+ end
93
+ def seek(*args)
94
+ super
95
+ @parser = nil
96
+ @fiber = nil
97
+ end
98
+ def rewind
99
+ super
100
+ @parser = nil
101
+ @fiber = nil
102
+ end
103
+
104
+ private
105
+
106
+ def parser
107
+ @parser ||= Parser.new
108
+ end
109
+
110
+ def fiber
111
+ # @see http://www.ruby-doc.org/core-2.1.4/Fiber.html
112
+ @fiber ||= Fiber.new do
113
+ if @io.respond_to?(:internal_encoding)
114
+ enc2 = @io.external_encoding
115
+ enc = @io.internal_encoding || '-'
116
+ if enc2
117
+ encoding = "#{enc2}:#{enc}"
118
+ else
119
+ encoding = enc
120
+ end
121
+ end
122
+ parser.raw_parse(@io, encoding: encoding) do |row|
123
+ Fiber.yield(row)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def FastCSV(*args, &block)
130
+ FastCSV.instance(*args, &block)
131
+ end