fastercsv 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # fastercsv.rb
4
+ #
5
+ # Created by James Edward Gray II on 2006-04-01.
6
+ # Copyright 2006 Gray Productions. All rights reserved.
7
+ #
8
+ # This file is just another name for faster_csv.rb.
9
+
10
+ require "faster_csv"
@@ -113,9 +113,52 @@ class TestCSVParsing < Test::Unit::TestCase
113
113
  assert_raise(FasterCSV::MalformedCSVError) do
114
114
  FasterCSV.parse_line("1,2\r,3", :row_sep => "\n")
115
115
  end
116
+
117
+ bad_data = <<-END_DATA.gsub(/^ +/, "")
118
+ line,1,abc
119
+ line,2,"def\nghi"
120
+
121
+ line,4,some\rjunk
122
+ line,5,jkl
123
+ END_DATA
124
+ lines = bad_data.to_a
125
+ assert_equal(6, lines.size)
126
+ assert_match(/\Aline,4/, lines.find { |l| l =~ /some\rjunk/ })
127
+
128
+ csv = FasterCSV.new(bad_data)
129
+ begin
130
+ loop do
131
+ assert_not_nil(csv.shift)
132
+ assert_send([csv.lineno, :<, 4])
133
+ end
134
+ rescue FasterCSV::MalformedCSVError
135
+ assert_equal( "Unquoted fields do not allow \\r or \\n (line 4).",
136
+ $!.message )
137
+ end
116
138
 
117
139
  assert_raise(FasterCSV::MalformedCSVError) do
118
140
  FasterCSV.parse_line('1,2,"3...')
119
141
  end
142
+
143
+ bad_data = <<-END_DATA.gsub(/^ +/, "")
144
+ line,1,abc
145
+ line,2,"def\nghi"
146
+
147
+ line,4,8'10"
148
+ line,5,jkl
149
+ END_DATA
150
+ lines = bad_data.to_a
151
+ assert_equal(6, lines.size)
152
+ assert_match(/\Aline,4/, lines.find { |l| l =~ /8'10"/ })
153
+
154
+ csv = FasterCSV.new(bad_data)
155
+ begin
156
+ loop do
157
+ assert_not_nil(csv.shift)
158
+ assert_send([csv.lineno, :<, 4])
159
+ end
160
+ rescue FasterCSV::MalformedCSVError
161
+ assert_equal("Unclosed quoted field on line 4.", $!.message)
162
+ end
120
163
  end
121
164
  end
@@ -15,6 +15,8 @@ class TestDataConverters < Test::Unit::TestCase
15
15
  @parser = FasterCSV.new(@data)
16
16
 
17
17
  @custom = lambda { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field }
18
+
19
+ @win_safe_time_str = Time.now.strftime("%a %b %d %H:%M:%S %Y")
18
20
  end
19
21
 
20
22
  def test_builtin_integer_converter
@@ -43,7 +45,7 @@ class TestDataConverters < Test::Unit::TestCase
43
45
 
44
46
  def test_builtin_date_converter
45
47
  # does convert
46
- assert_instance_of(Date, FasterCSV::Converters[:date][Time.now.to_s])
48
+ assert_instance_of(Date, FasterCSV::Converters[:date][@win_safe_time_str])
47
49
 
48
50
  # does not convert
49
51
  assert_instance_of(String, FasterCSV::Converters[:date]["junk"])
@@ -52,7 +54,7 @@ class TestDataConverters < Test::Unit::TestCase
52
54
  def test_builtin_date_time_converter
53
55
  # does convert
54
56
  assert_instance_of( DateTime,
55
- FasterCSV::Converters[:date_time][Time.now.to_s] )
57
+ FasterCSV::Converters[:date_time][@win_safe_time_str] )
56
58
 
57
59
  # does not convert
58
60
  assert_instance_of(String, FasterCSV::Converters[:date_time]["junk"])
@@ -110,8 +112,8 @@ class TestDataConverters < Test::Unit::TestCase
110
112
 
111
113
  def test_builtin_all_nested_combo_converter
112
114
  # setup parser...
113
- @data << ",#{Time.now}" # add a DateTime field
114
- @parser = FasterCSV.new(@data) # reset parser
115
+ @data << ",#{@win_safe_time_str}" # add a DateTime field
116
+ @parser = FasterCSV.new(@data) # reset parser
115
117
  assert_nothing_raised(Exception) { @parser.convert(:all) }
116
118
 
117
119
  # and use
@@ -151,6 +153,20 @@ class TestDataConverters < Test::Unit::TestCase
151
153
  assert_equal(["Numbers", ":integer", "1", ":float", 3], @parser.shift)
152
154
  end
153
155
 
156
+ def test_convert_with_custom_code_using_field_info_header
157
+ @parser = FasterCSV.new(@data, :headers => %w{one two three four five})
158
+
159
+ # define custom converter that uses field header information...
160
+ assert_nothing_raised(Exception) do
161
+ @parser.convert do |field, info|
162
+ info.header == "three" ? Integer(field) * 100 : field
163
+ end
164
+ end
165
+
166
+ # and use
167
+ assert_equal(["Numbers", ":integer", 100, ":float", "3.015"], @parser.shift.fields)
168
+ end
169
+
154
170
  def test_shortcut_interface
155
171
  assert_equal( ["Numbers", ":integer", 1, ":float", 3.015],
156
172
  FasterCSV.parse_line(@data, :converters => :numeric) )
@@ -163,4 +179,79 @@ class TestDataConverters < Test::Unit::TestCase
163
179
  FasterCSV.parse_line( @data, :converters => [ :numeric,
164
180
  @custom ] ) )
165
181
  end
182
+
183
+ def test_unconverted_fields
184
+ [ [ @data,
185
+ ["Numbers", :integer, 1, :float, 3.015],
186
+ %w{Numbers :integer 1 :float 3.015} ],
187
+ ["\n", Array.new, Array.new] ].each do |test, fields, unconverted|
188
+ row = nil
189
+ assert_nothing_raised(Exception) do
190
+ row = FasterCSV.parse_line( test,
191
+ :converters => [:numeric, @custom],
192
+ :unconverted_fields => true )
193
+ end
194
+ assert_not_nil(row)
195
+ assert_equal(fields, row)
196
+ assert_respond_to(row, :unconverted_fields)
197
+ assert_equal(unconverted, row.unconverted_fields)
198
+ end
199
+
200
+ data = <<-END_CSV.gsub(/^\s+/, "")
201
+ first,second,third
202
+ 1,2,3
203
+ END_CSV
204
+ row = nil
205
+ assert_nothing_raised(Exception) do
206
+ row = FasterCSV.parse_line( data,
207
+ :converters => :numeric,
208
+ :unconverted_fields => true,
209
+ :headers => :first_row )
210
+ end
211
+ assert_not_nil(row)
212
+ assert_equal([["first", 1], ["second", 2], ["third", 3]], row.to_a)
213
+ assert_respond_to(row, :unconverted_fields)
214
+ assert_equal(%w{1 2 3}, row.unconverted_fields)
215
+
216
+ assert_nothing_raised(Exception) do
217
+ row = FasterCSV.parse_line( data,
218
+ :converters => :numeric,
219
+ :unconverted_fields => true,
220
+ :headers => :first_row,
221
+ :return_headers => true )
222
+ end
223
+ assert_not_nil(row)
224
+ assert_equal( [%w{first first}, %w{second second}, %w{third third}],
225
+ row.to_a )
226
+ assert_respond_to(row, :unconverted_fields)
227
+ assert_equal(%w{first second third}, row.unconverted_fields)
228
+
229
+ assert_nothing_raised(Exception) do
230
+ row = FasterCSV.parse_line( data,
231
+ :converters => :numeric,
232
+ :unconverted_fields => true,
233
+ :headers => :first_row,
234
+ :return_headers => true,
235
+ :header_converters => :symbol )
236
+ end
237
+ assert_not_nil(row)
238
+ assert_equal( [[:first, "first"], [:second, "second"], [:third, "third"]],
239
+ row.to_a )
240
+ assert_respond_to(row, :unconverted_fields)
241
+ assert_equal(%w{first second third}, row.unconverted_fields)
242
+
243
+ assert_nothing_raised(Exception) do
244
+ row = FasterCSV.parse_line( data,
245
+ :converters => :numeric,
246
+ :unconverted_fields => true,
247
+ :headers => %w{my new headers},
248
+ :return_headers => true,
249
+ :header_converters => :symbol )
250
+ end
251
+ assert_not_nil(row)
252
+ assert_equal( [[:my, "my"], [:new, "new"], [:headers, "headers"]],
253
+ row.to_a )
254
+ assert_respond_to(row, :unconverted_fields)
255
+ assert_equal(Array.new, row.unconverted_fields)
256
+ end
166
257
  end
@@ -63,6 +63,25 @@ class TestFasterCSVFeatures < Test::Unit::TestCase
63
63
  assert_equal($/, FasterCSV.new(STDERR).instance_eval { @row_sep })
64
64
  end
65
65
 
66
+ def test_lineno
67
+ sample_data = <<-END_DATA.gsub(/^ +/, "")
68
+ line,1,abc
69
+ line,2,"def\nghi"
70
+
71
+ line,4,jkl
72
+ END_DATA
73
+ assert_equal(5, sample_data.to_a.size)
74
+
75
+ csv = FasterCSV.new(sample_data)
76
+ 4.times do |line_count|
77
+ assert_equal(line_count, csv.lineno)
78
+ assert_not_nil(csv.shift)
79
+ assert_equal(line_count + 1, csv.lineno)
80
+ end
81
+ assert_nil(csv.shift)
82
+ csv.close
83
+ end
84
+
66
85
  def test_unknown_options
67
86
  assert_raise(ArgumentError) do
68
87
  FasterCSV.new(String.new, :unknown => :error)
@@ -75,4 +94,11 @@ class TestFasterCSVFeatures < Test::Unit::TestCase
75
94
  FasterCSV.new(String.new, :col_sep => "|")
76
95
  end
77
96
  end
97
+
98
+ def test_version
99
+ assert_not_nil(FasterCSV::VERSION)
100
+ assert_instance_of(String, FasterCSV::VERSION)
101
+ assert(FasterCSV::VERSION.frozen?)
102
+ assert_match(/\A\d\.\d\.\d\Z/, FasterCSV::VERSION)
103
+ end
78
104
  end
@@ -43,6 +43,93 @@ class TestFasterCSVHeaders < Test::Unit::TestCase
43
43
  end
44
44
  end
45
45
 
46
+ def test_array_of_headers
47
+ # activate headers
48
+ csv = nil
49
+ assert_nothing_raised(Exception) do
50
+ csv = FasterCSV.parse(@data, :headers => [:my, :new, :headers])
51
+ end
52
+
53
+ # first data row - skipping headers
54
+ row = csv.shift
55
+ assert_not_nil(row)
56
+ assert_instance_of(FasterCSV::Row, row)
57
+ assert_equal( [[:my, "first"], [:new, "second"], [:headers, "third"]],
58
+ row.to_a )
59
+
60
+ # second data row
61
+ row = csv.shift
62
+ assert_not_nil(row)
63
+ assert_instance_of(FasterCSV::Row, row)
64
+ assert_equal([[:my, "A"], [:new, "B"], [:headers, "C"]], row.to_a)
65
+
66
+ # third data row
67
+ row = csv.shift
68
+ assert_not_nil(row)
69
+ assert_instance_of(FasterCSV::Row, row)
70
+ assert_equal([[:my, "1"], [:new, "2"], [:headers, "3"]], row.to_a)
71
+
72
+ # empty
73
+ assert_nil(csv.shift)
74
+
75
+ # with return and convert
76
+ assert_nothing_raised(Exception) do
77
+ csv = FasterCSV.parse(@data, :headers => [:my, :new, :headers],
78
+ :return_headers => true,
79
+ :header_converters => lambda { |h| h.to_s } )
80
+ end
81
+ row = csv.shift
82
+ assert_not_nil(row)
83
+ assert_instance_of(FasterCSV::Row, row)
84
+ assert_equal( [["my", :my], ["new", :new], ["headers", :headers]],
85
+ row.to_a )
86
+ assert(row.header_row?)
87
+ assert(!row.field_row?)
88
+ end
89
+
90
+ def test_csv_header_string
91
+ # activate headers
92
+ csv = nil
93
+ assert_nothing_raised(Exception) do
94
+ csv = FasterCSV.parse(@data, :headers => "my,new,headers")
95
+ end
96
+
97
+ # first data row - skipping headers
98
+ row = csv.shift
99
+ assert_not_nil(row)
100
+ assert_instance_of(FasterCSV::Row, row)
101
+ assert_equal([%w{my first}, %w{new second}, %w{headers third}], row.to_a)
102
+
103
+ # second data row
104
+ row = csv.shift
105
+ assert_not_nil(row)
106
+ assert_instance_of(FasterCSV::Row, row)
107
+ assert_equal([%w{my A}, %w{new B}, %w{headers C}], row.to_a)
108
+
109
+ # third data row
110
+ row = csv.shift
111
+ assert_not_nil(row)
112
+ assert_instance_of(FasterCSV::Row, row)
113
+ assert_equal([%w{my 1}, %w{new 2}, %w{headers 3}], row.to_a)
114
+
115
+ # empty
116
+ assert_nil(csv.shift)
117
+
118
+ # with return and convert
119
+ assert_nothing_raised(Exception) do
120
+ csv = FasterCSV.parse(@data, :headers => "my,new,headers",
121
+ :return_headers => true,
122
+ :header_converters => :symbol )
123
+ end
124
+ row = csv.shift
125
+ assert_not_nil(row)
126
+ assert_instance_of(FasterCSV::Row, row)
127
+ assert_equal( [[:my, "my"], [:new, "new"], [:headers, "headers"]],
128
+ row.to_a )
129
+ assert(row.header_row?)
130
+ assert(!row.field_row?)
131
+ end
132
+
46
133
  def test_return_headers
47
134
  # activate headers and request they are returned
48
135
  csv = nil
@@ -163,4 +163,86 @@ class TestFasterCSVInterface < Test::Unit::TestCase
163
163
  end
164
164
  assert_equal("2,4,6,\"Added\r\"\n8,10,\"Added\r\"\n", result)
165
165
  end
166
+
167
+ def test_instance
168
+ csv = String.new
169
+
170
+ first = nil
171
+ assert_nothing_raised(Exception) do
172
+ first = FasterCSV.instance(csv, :col_sep => ";")
173
+ first << %w{a b c}
174
+ end
175
+
176
+ assert_equal("a;b;c\n", csv)
177
+
178
+ second = nil
179
+ assert_nothing_raised(Exception) do
180
+ second = FasterCSV.instance(csv, :col_sep => ";")
181
+ second << [1, 2, 3]
182
+ end
183
+
184
+ assert_equal(first.object_id, second.object_id)
185
+ assert_equal("a;b;c\n1;2;3\n", csv)
186
+
187
+ # shortcuts
188
+ assert_equal(STDOUT, FasterCSV.instance.instance_eval { @io })
189
+ assert_equal(STDOUT, FasterCSV { |csv| csv.instance_eval { @io } })
190
+ assert_equal(STDOUT, FCSV.instance.instance_eval { @io })
191
+ assert_equal(STDOUT, FCSV { |csv| csv.instance_eval { @io } })
192
+ end
193
+
194
+ ### Test Alternate Interface ###
195
+
196
+ def test_csv_interface
197
+ require "csv"
198
+ data = ["Number", 42, "Tricky Field", 'This has embedded "quotes"!']
199
+ data_file = File.join(File.dirname(__FILE__), "temp_csv_data.csv")
200
+ CSV.open(data_file, "w") { |f| 10.times { f << data } }
201
+ csv = CSV.generate_line(data)
202
+ tests = { :foreach => Array.new,
203
+ :generate_line => csv,
204
+ :open => Array.new,
205
+ :parse => CSV.parse(csv),
206
+ :parse_w_block => Array.new,
207
+ :parse_line => CSV.parse_line(csv),
208
+ :readlines => CSV.readlines(data_file) }
209
+ CSV.foreach(data_file) { |row| tests[:foreach] << row }
210
+ CSV.open(data_file, "r") { |row| tests[:open] << row }
211
+ CSV.parse(([csv] * 3).join("\n")) { |row| tests[:parse_w_block] << row }
212
+ Object.send(:remove_const, :CSV)
213
+
214
+ assert_nothing_raised(Exception) do
215
+ FasterCSV.build_csv_interface
216
+ end
217
+
218
+ %w{ foreach
219
+ generate_line
220
+ open
221
+ parse
222
+ parse_line
223
+ readlines }.each do |meth|
224
+ assert_respond_to(::CSV, meth)
225
+ end
226
+
227
+ faster_csv = Array.new
228
+ CSV.foreach(data_file) { |row| faster_csv << row }
229
+ assert_equal(tests[:foreach], faster_csv)
230
+ assert_equal(tests[:generate_line], CSV.generate_line(data))
231
+ faster_csv.clear
232
+ CSV.open(data_file, "r") { |row| faster_csv << row }
233
+ assert_equal(tests[:open], faster_csv)
234
+ comp_file = data_file.sub("_csv_data", "_faster_csv_data")
235
+ CSV.open(comp_file, "w") { |f| 10.times { f << data } }
236
+ assert_equal(File.read(data_file), File.read(comp_file))
237
+ assert_equal(tests[:parse], CSV.parse(csv))
238
+ faster_csv.clear
239
+ CSV.parse(([csv] * 3).join("\n")) { |row| faster_csv << row }
240
+ assert_equal(tests[:parse_w_block], faster_csv)
241
+ assert_equal(tests[:parse_line], CSV.parse_line(csv))
242
+ assert_equal(tests[:readlines], CSV.readlines(data_file))
243
+
244
+ Object.send(:remove_const, :CSV)
245
+ load "csv.rb"
246
+ [data_file, comp_file].each { |file| File.unlink(file) }
247
+ end
166
248
  end
@@ -0,0 +1,154 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # tc_serialization.rb
4
+ #
5
+ # Created by James Edward Gray II on 2006-03-28.
6
+ # Copyright 2006 Gray Productions. All rights reserved.
7
+
8
+ require "test/unit"
9
+
10
+ require "faster_csv"
11
+
12
+ # An example of how to provide custom CSV serialization.
13
+ class Hash
14
+ def self.csv_load( meta, headers, fields )
15
+ self[*headers.zip(fields).flatten.map { |e| eval(e) }]
16
+ end
17
+
18
+ def csv_headers
19
+ keys.map { |key| key.inspect }
20
+ end
21
+
22
+ def csv_dump( headers )
23
+ headers.map { |header| fetch(eval(header)).inspect }
24
+ end
25
+ end
26
+
27
+ class TestSerialization < Test::Unit::TestCase
28
+
29
+ ### Classes Used to Test Serialization ###
30
+
31
+ class ReadOnlyName
32
+ def initialize( first, last )
33
+ @first, @last = first, last
34
+ end
35
+
36
+ attr_reader :first, :last
37
+
38
+ def ==( other )
39
+ %w{first last}.all? { |att| send(att) == other.send(att) }
40
+ end
41
+ end
42
+
43
+ Name = Struct.new(:first, :last)
44
+
45
+ class FullName < Name
46
+ def initialize( first, last, suffix = nil )
47
+ super(first, last)
48
+
49
+ @suffix = suffix
50
+ end
51
+
52
+ attr_accessor :suffix
53
+
54
+ def ==( other )
55
+ %w{first last suffix}.all? { |att| send(att) == other.send(att) }
56
+ end
57
+ end
58
+
59
+ ### Tests ###
60
+
61
+ def test_class_dump
62
+ @names = [ %w{James Gray},
63
+ %w{Dana Gray},
64
+ %w{Greg Brown} ].map do |first, last|
65
+ ReadOnlyName.new(first, last)
66
+ end
67
+
68
+ assert_nothing_raised(Exception) do
69
+ @data = FasterCSV.dump(@names)
70
+ end
71
+ assert_equal(<<-END_CLASS_DUMP.gsub(/^\s*/, ""), @data)
72
+ class,TestSerialization::ReadOnlyName
73
+ @first,@last
74
+ James,Gray
75
+ Dana,Gray
76
+ Greg,Brown
77
+ END_CLASS_DUMP
78
+ end
79
+
80
+ def test_struct_dump
81
+ @names = [ %w{James Gray},
82
+ %w{Dana Gray},
83
+ %w{Greg Brown} ].map do |first, last|
84
+ Name.new(first, last)
85
+ end
86
+
87
+ assert_nothing_raised(Exception) do
88
+ @data = FasterCSV.dump(@names)
89
+ end
90
+ assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
91
+ class,TestSerialization::Name
92
+ first=,last=
93
+ James,Gray
94
+ Dana,Gray
95
+ Greg,Brown
96
+ END_STRUCT_DUMP
97
+ end
98
+
99
+ def test_inherited_struct_dump
100
+ @names = [ %w{James Gray II},
101
+ %w{Dana Gray},
102
+ %w{Greg Brown} ].map do |first, last, suffix|
103
+ FullName.new(first, last, suffix)
104
+ end
105
+
106
+ assert_nothing_raised(Exception) do
107
+ @data = FasterCSV.dump(@names)
108
+ end
109
+ assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
110
+ class,TestSerialization::FullName
111
+ @suffix,first=,last=
112
+ II,James,Gray
113
+ ,Dana,Gray
114
+ ,Greg,Brown
115
+ END_STRUCT_DUMP
116
+ end
117
+
118
+ def test_load
119
+ %w{ test_class_dump
120
+ test_struct_dump
121
+ test_inherited_struct_dump }.each do |test|
122
+ send(test)
123
+ FasterCSV.load(@data).each do |loaded|
124
+ assert_instance_of(@names.first.class, loaded)
125
+ assert_equal(@names.shift, loaded)
126
+ end
127
+ end
128
+ end
129
+
130
+ def test_io
131
+ test_class_dump
132
+
133
+ data_file = File.join(File.dirname(__FILE__), "temp_test_data.csv")
134
+ FasterCSV.dump(@names, File.open(data_file, "w"))
135
+
136
+ assert(File.exist?(data_file))
137
+ assert_equal(<<-END_IO_DUMP.gsub(/^\s*/, ""), File.read(data_file))
138
+ class,TestSerialization::ReadOnlyName
139
+ @first,@last
140
+ James,Gray
141
+ Dana,Gray
142
+ Greg,Brown
143
+ END_IO_DUMP
144
+
145
+ assert_equal(@names, FasterCSV.load(File.open(data_file)))
146
+
147
+ File.unlink(data_file)
148
+ end
149
+
150
+ def test_custom_dump_and_load
151
+ obj = {1 => "simple", :test => Hash}
152
+ assert_equal(obj, FasterCSV.load(FasterCSV.dump([obj])).first)
153
+ end
154
+ end