fastercsv 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  Below is a complete listing of changes for each revision of FasterCSV.
4
4
 
5
+ == 0.2.0
6
+
7
+ * Added VERSION constant.
8
+ * Significantly improved test speed.
9
+ * Worked around Date::parse bug so tests will pass on Windows.
10
+ * Documented test procedure.
11
+ * Made FasterCSV#lineno CSV aware.
12
+ * Added line numbers to MalformedCSVError messages.
13
+ * <tt>:headers</tt> can now be set to an Array of headers to use.
14
+ * <tt>:headers</tt> can now be set to an external CSV String of headers to use.
15
+ * Added an <tt>:unconverted_fields</tt> options, so those can be returned
16
+ when needed.
17
+ * Provided support for the serialization of custom Ruby objects using CSV.
18
+ * Added CSV drop-in interface.
19
+ * Added header information to FieldInfo Struct for conversions by header.
20
+ * Added an alias to support <tt>require "fastercsv"</tt>.
21
+ * Added FCSV alias for FasterCSV.
22
+ * Added FasterCSV::instance and FasterCSV()/FCSV() shortcuts for easy output.
23
+
5
24
  == 0.1.9
6
25
 
7
26
  * Fixing the require "English" bug.
data/INSTALL CHANGED
@@ -21,3 +21,15 @@ Download the latest version of FasterCSV from the
21
21
  the root project directory and enter:
22
22
 
23
23
  $ sudo ruby setup.rb
24
+
25
+ == Running the Tests
26
+
27
+ If you would like to run FasterCSV's test suite on your system before installing
28
+ and you have Rake installed, just issue the following command from the root of
29
+ the project directory:
30
+
31
+ $ rake
32
+
33
+ If you do not have rake, use the following command instead:
34
+
35
+ $ ruby -I lib:test test/ts_all.rb
data/Rakefile CHANGED
@@ -37,14 +37,17 @@ end
37
37
 
38
38
  desc "Time FasterCSV and CSV"
39
39
  task :benchmark do
40
+ TESTS = 6
40
41
  path = "test/test_data.csv"
41
- sh %Q{time ruby -r csv -e 'CSV.foreach("#{path}") { |row| }'}
42
- sh %Q{time ruby -r lib/faster_csv -e 'FasterCSV.foreach("#{path}") { |row| }'}
42
+ sh %Q{time ruby -r csv -e } +
43
+ %Q{'#{TESTS}.times { CSV.foreach("#{path}") { |row| } }'}
44
+ sh %Q{time ruby -r lib/faster_csv -e } +
45
+ %Q{'#{TESTS}.times { FasterCSV.foreach("#{path}") { |row| } }'}
43
46
  end
44
47
 
45
48
  spec = Gem::Specification.new do |spec|
46
49
  spec.name = "fastercsv"
47
- spec.version = "0.1.9"
50
+ spec.version = "0.2.0"
48
51
  spec.platform = Gem::Platform::RUBY
49
52
  spec.summary = "FasterCSV is CSV, but faster, smaller, and cleaner."
50
53
 
data/TODO CHANGED
@@ -28,3 +28,4 @@ order.
28
28
  "Experiment ID: 3",,,,,,,,,,,,0.92
29
29
  ....
30
30
  * Add calculated fields.
31
+ * Examples, examples, examples...
@@ -0,0 +1,32 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # shortcut_interface.rb
4
+ #
5
+ # Created by James Edward Gray II on 2006-04-01.
6
+ # Copyright 2006 Gray Productions. All rights reserved.
7
+ #
8
+ # Feature implementation and example code by Ara.T.Howard.
9
+
10
+ require "faster_csv"
11
+
12
+ #
13
+ # So now it's this easy to write to STDOUT.
14
+ #
15
+ FCSV { |f| f << %w( a b c) << %w( d e f ) }
16
+
17
+ #
18
+ # Writing to a String.
19
+ #
20
+ FCSV(csv = '') do |f|
21
+ f << %w( q r s )
22
+ f << %w( x y z )
23
+ end
24
+ puts csv
25
+
26
+ #
27
+ # Writing to STDERR.
28
+ #
29
+ FCSV(STDERR) do |f|
30
+ f << %w( 0 1 2 )
31
+ f << %w( A B C )
32
+ end
@@ -67,7 +67,16 @@ require "stringio"
67
67
  # csv_string = ["CSV", "data"].to_csv # to CSV
68
68
  # csv_array = "CSV,String".parse_csv # from CSV
69
69
  #
70
+ # == Shortcut Interface
71
+ #
72
+ # FCSV { |csv_out| csv_out << %w{my data here} } # to STDOUT
73
+ # FCSV(csv = "") { |csv_str| csv_str << %w{my data here} } # to a String
74
+ # FCSV(STDERR) { |csv_err| csv_err << %w{my data here} } # to STDERR
75
+ #
70
76
  class FasterCSV
77
+ # The version of the installed library.
78
+ VERSION = "0.2.0".freeze
79
+
71
80
  #
72
81
  # A FasterCSV::Row is part Array and part Hash. It retains an order for the
73
82
  # fields and allows duplicates just as an Array would, but also allows you to
@@ -330,8 +339,9 @@ class FasterCSV
330
339
  #
331
340
  # <b><tt>index</tt></b>:: The zero-based index of the field in its row.
332
341
  # <b><tt>line</tt></b>:: The line of the data source this row is from.
342
+ # <b><tt>header</tt></b>:: The header for the column, when available.
333
343
  #
334
- FieldInfo = Struct.new(:index, :line)
344
+ FieldInfo = Struct.new(:index, :line, :header)
335
345
 
336
346
  #
337
347
  # This Hash holds the built-in converters of FasterCSV that can be accessed by
@@ -390,16 +400,159 @@ class FasterCSV
390
400
  # <b><tt>:col_sep</tt></b>:: <tt>","</tt>
391
401
  # <b><tt>:row_sep</tt></b>:: <tt>:auto</tt>
392
402
  # <b><tt>:converters</tt></b>:: +nil+
403
+ # <b><tt>:unconverted_fields</tt></b>:: +nil+
393
404
  # <b><tt>:headers</tt></b>:: +false+
394
405
  # <b><tt>:return_headers</tt></b>:: +false+
395
406
  # <b><tt>:header_converters</tt></b>:: +nil+
396
407
  #
397
- DEFAULT_OPTIONS = { :col_sep => ",",
398
- :row_sep => :auto,
399
- :converters => nil,
400
- :headers => false,
401
- :return_headers => false,
402
- :header_converters => nil }.freeze
408
+ DEFAULT_OPTIONS = { :col_sep => ",",
409
+ :row_sep => :auto,
410
+ :converters => nil,
411
+ :unconverted_fields => nil,
412
+ :headers => false,
413
+ :return_headers => false,
414
+ :header_converters => nil }.freeze
415
+
416
+ #
417
+ # This method will build a drop-in replacement for many of the standard CSV
418
+ # methods. It allows you to write code like:
419
+ #
420
+ # begin
421
+ # require "faster_csv"
422
+ # FasterCSV.build_csv_interface
423
+ # rescue LoadError
424
+ # require "csv"
425
+ # end
426
+ # # ... use CSV here ...
427
+ #
428
+ # This is not a complete interface with completely identical behavior.
429
+ # However, it is intended to be close enough that you won't notice the
430
+ # difference in most cases. CSV methods supported are:
431
+ #
432
+ # * foreach()
433
+ # * generate_line()
434
+ # * open()
435
+ # * parse()
436
+ # * parse_line()
437
+ # * readlines()
438
+ #
439
+ # Be warned that this interface is slower than vanilla FasterCSV due to the
440
+ # extra layer of method calls. Depending on usage, this can slow it down to
441
+ # near CSV speeds.
442
+ #
443
+ def self.build_csv_interface
444
+ Object.const_set(:CSV, Class.new).class_eval do
445
+ def self.foreach( path, rs = :auto, &block ) # :nodoc:
446
+ FasterCSV.foreach(path, :row_sep => rs, &block)
447
+ end
448
+
449
+ def self.generate_line( row, fs = ",", rs = "" ) # :nodoc:
450
+ FasterCSV.generate_line(row, :col_sep => fs, :row_sep => rs)
451
+ end
452
+
453
+ def self.open( path, mode, fs = ",", rs = :auto, &block ) # :nodoc:
454
+ if block and mode.include? "r"
455
+ FasterCSV.open(path, mode, :col_sep => fs, :row_sep => rs) do |csv|
456
+ csv.each(&block)
457
+ end
458
+ else
459
+ FasterCSV.open(path, mode, :col_sep => fs, :row_sep => rs, &block)
460
+ end
461
+ end
462
+
463
+ def self.parse( str_or_readable, fs = ",", rs = :auto, &block ) # :nodoc:
464
+ FasterCSV.parse(str_or_readable, :col_sep => fs, :row_sep => rs, &block)
465
+ end
466
+
467
+ def self.parse_line( src, fs = ",", rs = :auto ) # :nodoc:
468
+ FasterCSV.parse_line(src, :col_sep => fs, :row_sep => rs)
469
+ end
470
+
471
+ def self.readlines( path, rs = :auto ) # :nodoc:
472
+ FasterCSV.readlines(path, :row_sep => rs)
473
+ end
474
+ end
475
+ end
476
+
477
+ #
478
+ # This method allows you to serialize an Array of Ruby objects to a String or
479
+ # File of CSV data. This is not as powerful as Marshal or YAML, but perhaps
480
+ # useful for spreadsheet and database interaction.
481
+ #
482
+ # Out of the box, this method is intended to work with simple data objects or
483
+ # Structs. It will serialize a list of instance variables and/or
484
+ # Struct.members().
485
+ #
486
+ # If you need need more complicated serialization, you can control the process
487
+ # by adding methods to the class to be serialized.
488
+ #
489
+ # A class method csv_meta() is responsible for returning the first row of the
490
+ # document (as an Array). This row is considered to be a Hash of the form
491
+ # key_1,value_1,key_2,value_2,... FasterCSV::load() expects to find a class
492
+ # key with a value of the stringified class name and FasterCSV::dump() will
493
+ # create this, if you do not define this method. This method is only called
494
+ # on the first object of the Array.
495
+ #
496
+ # The next method you can provide is an instance method called csv_headers().
497
+ # This method is expected to return the second line of the document (again as
498
+ # an Array), which is to be used to give each column a header. By default,
499
+ # FasterCSV::load() will set an instance variable if the field header starts
500
+ # with an @ character or call send() passing the header as the method name and
501
+ # the field value as an argument. This method is only called on the first
502
+ # object of the Array.
503
+ #
504
+ # Finally, you can provide an instance method called csv_dump(), which will
505
+ # be passed the headers. This should return an Array of fields that can be
506
+ # serialized for this object. This method is called once for every object in
507
+ # the Array.
508
+ #
509
+ # The +io+ parameter can be used to serialize to a File, and +options+ can be
510
+ # anything FasterCSV::new() accepts.
511
+ #
512
+ def self.dump( ary_of_objs, io = "", options = Hash.new )
513
+ obj_template = ary_of_objs.first
514
+
515
+ csv = FasterCSV.new(io, options)
516
+
517
+ # write meta information
518
+ begin
519
+ csv << obj_template.class.csv_meta
520
+ rescue NoMethodError
521
+ csv << [:class, obj_template.class]
522
+ end
523
+
524
+ # write headers
525
+ begin
526
+ headers = obj_template.csv_headers
527
+ rescue NoMethodError
528
+ headers = obj_template.instance_variables.sort
529
+ if obj_template.class.ancestors.find { |cls| cls.to_s =~ /\AStruct\b/ }
530
+ headers += obj_template.members.map { |mem| "#{mem}=" }.sort
531
+ end
532
+ end
533
+ csv << headers
534
+
535
+ # serialize each object
536
+ ary_of_objs.each do |obj|
537
+ begin
538
+ csv << obj.csv_dump(headers)
539
+ rescue NoMethodError
540
+ csv << headers.map do |var|
541
+ if var[0] == ?@
542
+ obj.instance_variable_get(var)
543
+ else
544
+ obj[var[0..-2]]
545
+ end
546
+ end
547
+ end
548
+ end
549
+
550
+ if io.is_a? String
551
+ csv.string
552
+ else
553
+ csv.close
554
+ end
555
+ end
403
556
 
404
557
  #
405
558
  # :call-seq:
@@ -508,6 +661,77 @@ class FasterCSV
508
661
  (new("", options) << row).string
509
662
  end
510
663
 
664
+ #
665
+ # This method will return a FasterCSV instance, just like FasterCSV::new(),
666
+ # but the instance will be cached and returned for all future calls to this
667
+ # method for the same +data+ object (tested by Object#object_id()) with the
668
+ # same +options+
669
+ #
670
+ # If a block is given, the instance is passed to the block and the return
671
+ # value becomes the return value of the block.
672
+ #
673
+ def self.instance( data = STDOUT, options = Hash.new )
674
+ # create a _signature_ for this method call, data object and options
675
+ sig = [data.object_id] +
676
+ options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s })
677
+
678
+ # fetch or create the instance for this signature
679
+ @@instances ||= Hash.new
680
+ instance = (@@instances[sig] ||= new(data, options))
681
+
682
+ if block_given?
683
+ yield instance # run block, if given, returning result
684
+ else
685
+ instance # or return the instance
686
+ end
687
+ end
688
+
689
+ #
690
+ # This method is the reading counterpart to FasterCSV::dump(). See that
691
+ # method for a detailed description of the process.
692
+ #
693
+ # You can customize loading by adding a class method called csv_load() which
694
+ # will be passed a Hash of meta information, an Array of headers, and an Array
695
+ # of fields for the object the method is expected to return.
696
+ #
697
+ # Remember that all fields will be Strings after this load. If you need
698
+ # something else, use +options+ to setup converters or provide a custom
699
+ # csv_load() implementation.
700
+ #
701
+ def self.load( io_or_str, options = Hash.new )
702
+ csv = FasterCSV.new(io_or_str, options)
703
+
704
+ # load meta information
705
+ meta = Hash[*csv.shift]
706
+ cls = meta["class"].split("::").inject(Object) do |c, const|
707
+ c.const_get(const)
708
+ end
709
+
710
+ # load headers
711
+ headers = csv.shift
712
+
713
+ # unserialize each object stored in the file
714
+ results = csv.inject(Array.new) do |all, row|
715
+ begin
716
+ obj = cls.csv_load(meta, headers, row)
717
+ rescue NoMethodError
718
+ obj = cls.allocate
719
+ headers.zip(row) do |name, value|
720
+ if name[0] == ?@
721
+ obj.instance_variable_set(name, value)
722
+ else
723
+ obj.send(name, value)
724
+ end
725
+ end
726
+ end
727
+ all << obj
728
+ end
729
+
730
+ csv.close unless io_or_str.is_a? String
731
+
732
+ results
733
+ end
734
+
511
735
  #
512
736
  # :call-seq:
513
737
  # open( filename, mode="r", options = Hash.new ) { |faster_csv| ... }
@@ -541,7 +765,6 @@ class FasterCSV
541
765
  # * fsync()
542
766
  # * ioctl()
543
767
  # * isatty()
544
- # * lineno()
545
768
  # * pid()
546
769
  # * pos()
547
770
  # * reopen()
@@ -663,10 +886,24 @@ class FasterCSV
663
886
  # Hash and/or lambdas that handle custom
664
887
  # conversion. A single converter
665
888
  # doesn't have to be in an Array.
889
+ # <b><tt>:unconverted_fields</tt></b>:: If set to +true+, an
890
+ # unconverted_fields() method will be
891
+ # added to all returned rows (Array or
892
+ # FasterCSV::Row) that will return the
893
+ # fields as they were before convertion.
894
+ # Note that <tt>:headers</tt> supplied
895
+ # by Array or String were not fields of
896
+ # the document and thus will have an
897
+ # empty Array attached.
666
898
  # <b><tt>:headers</tt></b>:: If set to <tt>:first_row</tt> or
667
899
  # +true+, the initial row of the CSV
668
900
  # file will be treated as a row of
669
- # headers. This setting causes
901
+ # headers. If set to an Array, the
902
+ # contents will be used as the headers.
903
+ # If set to a String, the String is run
904
+ # through a call of
905
+ # FasterCSV::parse_line() to produce an
906
+ # Array of headers. This setting causes
670
907
  # FasterCSV.shift() to return rows as
671
908
  # FasterCSV::Row objects instead of
672
909
  # Arrays.
@@ -701,16 +938,24 @@ class FasterCSV
701
938
  unless options.empty?
702
939
  raise ArgumentError, "Unknown options: #{options.keys.join(', ')}."
703
940
  end
941
+
942
+ # track our own lineno since IO gets confused about line-ends is CSV fields
943
+ @lineno = 0
704
944
  end
705
945
 
946
+ #
947
+ # The line number of the last row read from this file. Fields with nested
948
+ # line-end characters will not affect this count.
949
+ #
950
+ attr_reader :lineno
951
+
706
952
  ### IO and StringIO Delegation ###
707
953
 
708
954
  extend Forwardable
709
955
  def_delegators :@io, :binmode, :close, :close_read, :close_write, :closed?,
710
956
  :eof, :eof?, :fcntl, :fileno, :flush, :fsync, :ioctl,
711
- :isatty, :lineno, :pid, :pos, :reopen, :rewind, :seek,
712
- :stat, :string, :sync, :sync=, :tell, :to_i, :to_io,
713
- :tty?
957
+ :isatty, :pid, :pos, :reopen, :rewind, :seek, :stat,
958
+ :string, :sync, :sync=, :tell, :to_i, :to_io, :tty?
714
959
 
715
960
  ### End Delegation ###
716
961
 
@@ -820,6 +1065,21 @@ class FasterCSV
820
1065
  # The data source must be open for reading.
821
1066
  #
822
1067
  def shift
1068
+ #########################################################################
1069
+ ### This method is purposefully kept a bit long as simple conditional ###
1070
+ ### checks are faster than numerous (expensive) method calls. ###
1071
+ #########################################################################
1072
+
1073
+ # handle headers not based on document content
1074
+ if header_row? and @return_headers and
1075
+ [Array, String].include? @use_headers.class
1076
+ if @unconverted_fields
1077
+ return add_unconverted_fields(parse_headers, Array.new)
1078
+ else
1079
+ return parse_headers
1080
+ end
1081
+ end
1082
+
823
1083
  # begin with a blank line, so we can always add to it
824
1084
  line = ""
825
1085
 
@@ -838,7 +1098,14 @@ class FasterCSV
838
1098
  # I believe a blank line should be an <tt>Array.new</tt>, not
839
1099
  # CSV's <tt>[nil]</tt>
840
1100
  #
841
- return Array.new if parse.empty?
1101
+ if parse.empty?
1102
+ @lineno += 1
1103
+ if @unconverted_fields
1104
+ return add_unconverted_fields(Array.new, Array.new)
1105
+ else
1106
+ return Array.new
1107
+ end
1108
+ end
842
1109
 
843
1110
  #
844
1111
  # shave leading empty fields if needed, because the main parser chokes
@@ -863,7 +1130,8 @@ class FasterCSV
863
1130
  $2
864
1131
  else
865
1132
  # or throw an Exception
866
- raise MalformedCSVError, 'Unquoted fields do not allow \r or \n.'
1133
+ raise MalformedCSVError, "Unquoted fields do not allow " +
1134
+ "\\r or \\n (line #{lineno + 1})."
867
1135
  end
868
1136
  end
869
1137
  else # we found a quoted field...
@@ -874,15 +1142,28 @@ class FasterCSV
874
1142
 
875
1143
  # if parse is empty?(), we found all the fields on the line...
876
1144
  if parse.empty?
877
- # convert fields if needed...
878
- csv = convert_fields(csv) unless header_row? or @converters.empty?
1145
+ @lineno += 1
1146
+
1147
+ # save fields unconverted fields, if needed...
1148
+ unconverted = csv.dup if @unconverted_fields
1149
+
1150
+ # convert fields, if needed...
1151
+ csv = convert_fields(csv) unless @use_headers or @converters.empty?
879
1152
  # parse out header rows and handle FasterCSV::Row conversions...
880
1153
  csv = parse_headers(csv) if @use_headers
1154
+
1155
+ # inject unconverted fields and accessor, if requested...
1156
+ if @unconverted_fields and not csv.respond_to? :unconverted_fields
1157
+ add_unconverted_fields(csv, unconverted)
1158
+ end
1159
+
881
1160
  # return the results
882
1161
  break csv
883
1162
  end
884
1163
  # if we're not empty?() but at eof?(), a quoted field wasn't closed...
885
- raise MalformedCSVError, "Unclosed quoted field." if @io.eof?
1164
+ if @io.eof?
1165
+ raise MalformedCSVError, "Unclosed quoted field on line #{lineno + 1}."
1166
+ end
886
1167
  # otherwise, we need to loop and pull some more data to complete the row
887
1168
  end
888
1169
  end
@@ -966,7 +1247,14 @@ class FasterCSV
966
1247
  # are set. When +field_name+ is <tt>:header_converters</tt> header converters
967
1248
  # are added instead.
968
1249
  #
1250
+ # The <tt>:unconverted_fields</tt> option is also actived for
1251
+ # <tt>:converters</tt> calls, if requested.
1252
+ #
969
1253
  def init_converters( options, field_name = :converters )
1254
+ if field_name == :converters
1255
+ @unconverted_fields = options.delete(:unconverted_fields)
1256
+ end
1257
+
970
1258
  instance_variable_set("@#{field_name}", Array.new)
971
1259
 
972
1260
  # find the correct method to add the coverters
@@ -996,6 +1284,7 @@ class FasterCSV
996
1284
  @use_headers = options.delete(:headers)
997
1285
  @return_headers = options.delete(:return_headers)
998
1286
 
1287
+ # headers must be delayed until shift(), in case they need a row of content
999
1288
  @headers = nil
1000
1289
 
1001
1290
  init_converters(options, :header_converters)
@@ -1028,24 +1317,22 @@ class FasterCSV
1028
1317
 
1029
1318
  #
1030
1319
  # Processes +fields+ with <tt>@converters</tt>, or <tt>@header_converters</tt>
1031
- # if this is a header_row?(), returning the converted field set. Any
1320
+ # if +headers+ is passed as +true+, returning the converted field set. Any
1032
1321
  # converter that changes the field into something other than a String halts
1033
1322
  # the pipeline of conversion for that field. This is primarily an efficiency
1034
1323
  # shortcut.
1035
1324
  #
1036
- def convert_fields( fields )
1037
- converters = if header_row? # see if we are converting headers or fields
1038
- @header_converters
1039
- else
1040
- @converters
1041
- end
1325
+ def convert_fields( fields, headers = false )
1326
+ # see if we are converting headers or fields
1327
+ converters = headers ? @header_converters : @converters
1042
1328
 
1043
1329
  fields.enum_for(:each_with_index).map do |field, index| # map_with_index
1044
1330
  converters.each do |converter|
1045
1331
  field = if converter.arity == 1 # straight field converter
1046
1332
  converter[field]
1047
1333
  else # FieldInfo converter
1048
- converter[field, FieldInfo.new(index, @io.lineno)]
1334
+ header = @use_headers && !headers ? @headers[index] : nil
1335
+ converter[field, FieldInfo.new(index, lineno, header)]
1049
1336
  end
1050
1337
  break unless field.is_a? String # short-curcuit pipeline for speed
1051
1338
  end
@@ -1060,18 +1347,56 @@ class FasterCSV
1060
1347
  # converters) or by reading past them to return a field row. Headers are also
1061
1348
  # saved in <tt>@headers</tt> for use in future rows.
1062
1349
  #
1063
- def parse_headers( row )
1064
- if @headers.nil? # header row
1065
- @headers = convert_fields(row) # save
1066
- if @return_headers # return the headers
1067
- FasterCSV::Row.new(@headers, row, true)
1068
- else # skip to next field row
1069
- shift
1350
+ # When +nil+, +row+ is assumed to be a header row not based on an actual row
1351
+ # of the stream.
1352
+ #
1353
+ def parse_headers( row = nil )
1354
+ if @headers.nil? # header row
1355
+ @headers = case @use_headers # save headers
1356
+ when Array then @use_headers # Array of headers
1357
+ when String then self.class.parse_line(@use_headers) # CSV header String
1358
+ else row # first row headers
1359
+ end
1360
+
1361
+ # prepare converted and unconverted copies
1362
+ row = @headers if row.nil?
1363
+ @headers = convert_fields(@headers, true)
1364
+
1365
+ if @return_headers # return headers
1366
+ return FasterCSV::Row.new(@headers, row, true)
1367
+ elsif not [Array, String].include? @use_headers.class # skip to field row
1368
+ return shift
1070
1369
  end
1071
- else # field row
1072
- FasterCSV::Row.new(@headers, row)
1073
1370
  end
1371
+
1372
+ FasterCSV::Row.new(@headers, convert_fields(row)) # field row
1074
1373
  end
1374
+
1375
+ #
1376
+ # Thiw methods injects an instance variable <tt>unconverted_fields</tt> into
1377
+ # +row+ and an accessor method for it called unconverted_fields(). The
1378
+ # variable is set to the contents of +fields+.
1379
+ #
1380
+ def add_unconverted_fields( row, fields )
1381
+ class << row
1382
+ attr_reader :unconverted_fields
1383
+ end
1384
+ row.instance_eval { @unconverted_fields = fields }
1385
+ row
1386
+ end
1387
+ end
1388
+
1389
+ # Another name for FasterCSV.
1390
+ FCSV = FasterCSV
1391
+
1392
+ # Another name for FasterCSV::instance().
1393
+ def FasterCSV( *args, &block )
1394
+ FasterCSV.instance(*args, &block)
1395
+ end
1396
+
1397
+ # Another name for FCSV::instance().
1398
+ def FCSV( *args, &block )
1399
+ FCSV.instance(*args, &block)
1075
1400
  end
1076
1401
 
1077
1402
  class Array