b2b2dot0-fastercsv 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,164 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # tc_csv_parsing.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-10-31.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "test/unit"
9
+
10
+ require "faster_csv"
11
+
12
+ #
13
+ # Following tests are my interpretation of the
14
+ # {CSV RCF}[http://www.ietf.org/rfc/rfc4180.txt]. I only deviate from that
15
+ # document in one place (intentionally) and that is to make the default row
16
+ # separator <tt>$/</tt>.
17
+ #
18
+ class TestCSVParsing < Test::Unit::TestCase
19
+ def test_mastering_regex_example
20
+ ex = %Q{Ten Thousand,10000, 2710 ,,"10,000","It's ""10 Grand"", baby",10K}
21
+ assert_equal( [ "Ten Thousand", "10000", " 2710 ", nil, "10,000",
22
+ "It's \"10 Grand\", baby", "10K" ],
23
+ FasterCSV.parse_line(ex) )
24
+ end
25
+
26
+ # Pulled from: http://www.ruby-lang.org/cgi-bin/cvsweb.cgi/ruby/test/csv/test_csv.rb?rev=1.12.2.2;content-type=text%2Fplain
27
+ def test_std_lib_csv
28
+ [ ["\t", ["\t"]],
29
+ ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
30
+ ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
31
+ ["\"\"\"\n\",\"\"\"\n\"", ["\"\n", "\"\n"]],
32
+ ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
33
+ ["\"\"", [""]],
34
+ ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
35
+ ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
36
+ ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
37
+ ["foo,\"\",baz", ["foo", "", "baz"]],
38
+ ["\",\"", [","]],
39
+ ["foo", ["foo"]],
40
+ [",,", [nil, nil, nil]],
41
+ [",", [nil, nil]],
42
+ ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
43
+ ["foo,,baz", ["foo", nil, "baz"]],
44
+ ["\"\"\"\r\",\"\"\"\r\"", ["\"\r", "\"\r"]],
45
+ ["\",\",\",\"", [",", ","]],
46
+ ["foo,bar,", ["foo", "bar", nil]],
47
+ [",foo,bar", [nil, "foo", "bar"]],
48
+ ["foo,bar", ["foo", "bar"]],
49
+ [";", [";"]],
50
+ ["\t,\t", ["\t", "\t"]],
51
+ ["foo,\"\r\n\r\",baz", ["foo", "\r\n\r", "baz"]],
52
+ ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
53
+ ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]],
54
+ [";,;", [";", ";"]] ].each do |csv_test|
55
+ assert_equal(csv_test.last, FasterCSV.parse_line(csv_test.first))
56
+ end
57
+
58
+ [ ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
59
+ ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
60
+ ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
61
+ ["\"\"", [""]],
62
+ ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
63
+ ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
64
+ ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
65
+ ["foo,\"\",baz", ["foo", "", "baz"]],
66
+ ["foo", ["foo"]],
67
+ [",,", [nil, nil, nil]],
68
+ [",", [nil, nil]],
69
+ ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
70
+ ["foo,,baz", ["foo", nil, "baz"]],
71
+ ["foo,bar", ["foo", "bar"]],
72
+ ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
73
+ ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]] ].each do |csv_test|
74
+ assert_equal(csv_test.last, FasterCSV.parse_line(csv_test.first))
75
+ end
76
+ end
77
+
78
+ # From: http://ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-core/6496
79
+ def test_aras_edge_cases
80
+ [ [%Q{a,b}, ["a", "b"]],
81
+ [%Q{a,"""b"""}, ["a", "\"b\""]],
82
+ [%Q{a,"""b"}, ["a", "\"b"]],
83
+ [%Q{a,"b"""}, ["a", "b\""]],
84
+ [%Q{a,"\nb"""}, ["a", "\nb\""]],
85
+ [%Q{a,"""\nb"}, ["a", "\"\nb"]],
86
+ [%Q{a,"""\nb\n"""}, ["a", "\"\nb\n\""]],
87
+ [%Q{a,"""\nb\n""",\nc}, ["a", "\"\nb\n\"", nil]],
88
+ [%Q{a,,,}, ["a", nil, nil, nil]],
89
+ [%Q{,}, [nil, nil]],
90
+ [%Q{"",""}, ["", ""]],
91
+ [%Q{""""}, ["\""]],
92
+ [%Q{"""",""}, ["\"",""]],
93
+ [%Q{,""}, [nil,""]],
94
+ [%Q{,"\r"}, [nil,"\r"]],
95
+ [%Q{"\r\n,"}, ["\r\n,"]],
96
+ [%Q{"\r\n,",}, ["\r\n,", nil]] ].each do |edge_case|
97
+ assert_equal(edge_case.last, FasterCSV.parse_line(edge_case.first))
98
+ end
99
+ end
100
+
101
+ def test_james_edge_cases
102
+ # A read at eof? should return nil.
103
+ assert_equal(nil, FasterCSV.parse_line(""))
104
+ #
105
+ # With CSV it's impossible to tell an empty line from a line containing a
106
+ # single +nil+ field. The standard CSV library returns <tt>[nil]</tt>
107
+ # in these cases, but <tt>Array.new</tt> makes more sense to me.
108
+ #
109
+ assert_equal(Array.new, FasterCSV.parse_line("\n1,2,3\n"))
110
+ end
111
+
112
+ def test_malformed_csv
113
+ assert_raise(FasterCSV::MalformedCSVError) do
114
+ FasterCSV.parse_line("1,2\r,3", :row_sep => "\n")
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
138
+
139
+ assert_raise(FasterCSV::MalformedCSVError) do
140
+ FasterCSV.parse_line('1,2,"3...')
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("Illegal quoting on line 4.", $!.message)
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # tc_csv_writing.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-11-14.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "test/unit"
9
+
10
+ require "faster_csv"
11
+
12
+ class TestFasterCSVWriting < Test::Unit::TestCase
13
+ def test_writing
14
+ [ ["\t", ["\t"]],
15
+ ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
16
+ ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
17
+ ["\"\"\"\n\",\"\"\"\n\"", ["\"\n", "\"\n"]],
18
+ ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
19
+ ["\"\"", [""]],
20
+ ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
21
+ ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
22
+ ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
23
+ ["foo,\"\",baz", ["foo", "", "baz"]],
24
+ ["\",\"", [","]],
25
+ ["foo", ["foo"]],
26
+ [",,", [nil, nil, nil]],
27
+ [",", [nil, nil]],
28
+ ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
29
+ ["foo,,baz", ["foo", nil, "baz"]],
30
+ ["\"\"\"\r\",\"\"\"\r\"", ["\"\r", "\"\r"]],
31
+ ["\",\",\",\"", [",", ","]],
32
+ ["foo,bar,", ["foo", "bar", nil]],
33
+ [",foo,bar", [nil, "foo", "bar"]],
34
+ ["foo,bar", ["foo", "bar"]],
35
+ [";", [";"]],
36
+ ["\t,\t", ["\t", "\t"]],
37
+ ["foo,\"\r\n\r\",baz", ["foo", "\r\n\r", "baz"]],
38
+ ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
39
+ ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]],
40
+ [";,;", [";", ";"]],
41
+ ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
42
+ ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
43
+ ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
44
+ ["\"\"", [""]],
45
+ ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
46
+ ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
47
+ ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
48
+ ["foo,\"\",baz", ["foo", "", "baz"]],
49
+ ["foo", ["foo"]],
50
+ [",,", [nil, nil, nil]],
51
+ [",", [nil, nil]],
52
+ ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
53
+ ["foo,,baz", ["foo", nil, "baz"]],
54
+ ["foo,bar", ["foo", "bar"]],
55
+ ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
56
+ ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]],
57
+ [%Q{a,b}, ["a", "b"]],
58
+ [%Q{a,"""b"""}, ["a", "\"b\""]],
59
+ [%Q{a,"""b"}, ["a", "\"b"]],
60
+ [%Q{a,"b"""}, ["a", "b\""]],
61
+ [%Q{a,"\nb"""}, ["a", "\nb\""]],
62
+ [%Q{a,"""\nb"}, ["a", "\"\nb"]],
63
+ [%Q{a,"""\nb\n"""}, ["a", "\"\nb\n\""]],
64
+ [%Q{a,"""\nb\n""",}, ["a", "\"\nb\n\"", nil]],
65
+ [%Q{a,,,}, ["a", nil, nil, nil]],
66
+ [%Q{,}, [nil, nil]],
67
+ [%Q{"",""}, ["", ""]],
68
+ [%Q{""""}, ["\""]],
69
+ [%Q{"""",""}, ["\"",""]],
70
+ [%Q{,""}, [nil,""]],
71
+ [%Q{,"\r"}, [nil,"\r"]],
72
+ [%Q{"\r\n,"}, ["\r\n,"]],
73
+ [%Q{"\r\n,",}, ["\r\n,", nil]] ].each do |test_case|
74
+ assert_equal( test_case.first + $/,
75
+ FasterCSV.generate_line(test_case.last) )
76
+ end
77
+ end
78
+
79
+ def test_col_sep
80
+ assert_equal( "a;b;;c\n", FasterCSV.generate_line( ["a", "b", nil, "c"],
81
+ :col_sep => ";" ) )
82
+ assert_equal( "a\tb\t\tc\n", FasterCSV.generate_line( ["a", "b", nil, "c"],
83
+ :col_sep => "\t" ) )
84
+ end
85
+
86
+ def test_row_sep
87
+ assert_equal( "a,b,,c\r\n", FasterCSV.generate_line( ["a", "b", nil, "c"],
88
+ :row_sep => "\r\n" ) )
89
+ end
90
+
91
+ def test_force_quotes
92
+ assert_equal( %Q{"1","b","","already ""quoted"""\n},
93
+ FasterCSV.generate_line( [1, "b", nil, %Q{already "quoted"}],
94
+ :force_quotes => true ) )
95
+ end
96
+ end
@@ -0,0 +1,260 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # tc_data_converters.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-12-30.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "test/unit"
9
+
10
+ require "faster_csv"
11
+
12
+ class TestDataConverters < Test::Unit::TestCase
13
+ def setup
14
+ @data = "Numbers,:integer,1,:float,3.015"
15
+ @parser = FasterCSV.new(@data)
16
+
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")
20
+ end
21
+
22
+ def test_builtin_integer_converter
23
+ # does convert
24
+ [-5, 1, 10000000000].each do |n|
25
+ assert_equal(n, FasterCSV::Converters[:integer][n.to_s])
26
+ end
27
+
28
+ # does not convert
29
+ (%w{junk 1.0} + [""]).each do |str|
30
+ assert_equal(str, FasterCSV::Converters[:integer][str])
31
+ end
32
+ end
33
+
34
+ def test_builtin_float_converter
35
+ # does convert
36
+ [-5.1234, 0, 2.3e-11].each do |n|
37
+ assert_equal(n, FasterCSV::Converters[:float][n.to_s])
38
+ end
39
+
40
+ # does not convert
41
+ (%w{junk 1..0 .015F} + [""]).each do |str|
42
+ assert_equal(str, FasterCSV::Converters[:float][str])
43
+ end
44
+ end
45
+
46
+ def test_builtin_date_converter
47
+ # does convert
48
+ assert_instance_of(
49
+ Date,
50
+ FasterCSV::Converters[:date][@win_safe_time_str.sub(/\d+:\d+:\d+ /, "")]
51
+ )
52
+
53
+ # does not convert
54
+ assert_instance_of(String, FasterCSV::Converters[:date]["junk"])
55
+ end
56
+
57
+ def test_builtin_date_time_converter
58
+ # does convert
59
+ assert_instance_of( DateTime,
60
+ FasterCSV::Converters[:date_time][@win_safe_time_str] )
61
+
62
+ # does not convert
63
+ assert_instance_of(String, FasterCSV::Converters[:date_time]["junk"])
64
+ end
65
+
66
+ def test_convert_with_builtin
67
+ # setup parser...
68
+ assert(@parser.respond_to?(:convert))
69
+ assert_nothing_raised(Exception) { @parser.convert(:integer) }
70
+
71
+ # and use
72
+ assert_equal(["Numbers", ":integer", 1, ":float", "3.015"], @parser.shift)
73
+
74
+ setup # reset
75
+
76
+ # setup parser...
77
+ assert_nothing_raised(Exception) { @parser.convert(:float) }
78
+
79
+ # and use
80
+ assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015], @parser.shift)
81
+ end
82
+
83
+ def test_convert_order
84
+ # floats first, then integers...
85
+ assert_nothing_raised(Exception) do
86
+ @parser.convert(:float)
87
+ @parser.convert(:integer)
88
+ end
89
+
90
+ # gets us nothing but floats
91
+ assert_equal( [String, String, Float, String, Float],
92
+ @parser.shift.map { |field| field.class } )
93
+
94
+ setup # reset
95
+
96
+ # integers have precendance...
97
+ assert_nothing_raised(Exception) do
98
+ @parser.convert(:integer)
99
+ @parser.convert(:float)
100
+ end
101
+
102
+ # gives us proper number conversion
103
+ assert_equal( [String, String, Fixnum, String, Float],
104
+ @parser.shift.map { |field| field.class } )
105
+ end
106
+
107
+ def test_builtin_numeric_combo_converter
108
+ # setup parser...
109
+ assert_nothing_raised(Exception) { @parser.convert(:numeric) }
110
+
111
+ # and use
112
+ assert_equal( [String, String, Fixnum, String, Float],
113
+ @parser.shift.map { |field| field.class } )
114
+ end
115
+
116
+ def test_builtin_all_nested_combo_converter
117
+ # setup parser...
118
+ @data << ",#{@win_safe_time_str}" # add a DateTime field
119
+ @parser = FasterCSV.new(@data) # reset parser
120
+ assert_nothing_raised(Exception) { @parser.convert(:all) }
121
+
122
+ # and use
123
+ assert_equal( [String, String, Fixnum, String, Float, DateTime],
124
+ @parser.shift.map { |field| field.class } )
125
+ end
126
+
127
+ def test_convert_with_custom_code
128
+ # define custom converter...
129
+ assert_nothing_raised(Exception) do
130
+ @parser.convert { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field }
131
+ end
132
+
133
+ # and use
134
+ assert_equal(["Numbers", :integer, "1", :float, "3.015"], @parser.shift)
135
+
136
+ setup # reset
137
+
138
+ # mix built-in and custom...
139
+ assert_nothing_raised(Exception) { @parser.convert(:numeric) }
140
+ assert_nothing_raised(Exception) { @parser.convert(&@custom) }
141
+
142
+ # and use
143
+ assert_equal(["Numbers", :integer, 1, :float, 3.015], @parser.shift)
144
+ end
145
+
146
+ def test_convert_with_custom_code_using_field_info
147
+ # define custom converter that uses field information...
148
+ assert_nothing_raised(Exception) do
149
+ @parser.convert do |field, info|
150
+ assert_equal(1, info.line)
151
+ info.index == 4 ? Float(field).floor : field
152
+ end
153
+ end
154
+
155
+ # and use
156
+ assert_equal(["Numbers", ":integer", "1", ":float", 3], @parser.shift)
157
+ end
158
+
159
+ def test_convert_with_custom_code_using_field_info_header
160
+ @parser = FasterCSV.new(@data, :headers => %w{one two three four five})
161
+
162
+ # define custom converter that uses field header information...
163
+ assert_nothing_raised(Exception) do
164
+ @parser.convert do |field, info|
165
+ info.header == "three" ? Integer(field) * 100 : field
166
+ end
167
+ end
168
+
169
+ # and use
170
+ assert_equal(["Numbers", ":integer", 100, ":float", "3.015"], @parser.shift.fields)
171
+ end
172
+
173
+ def test_shortcut_interface
174
+ assert_equal( ["Numbers", ":integer", 1, ":float", 3.015],
175
+ FasterCSV.parse_line(@data, :converters => :numeric) )
176
+
177
+ assert_equal( ["Numbers", ":integer", 1, ":float", 3.015],
178
+ FasterCSV.parse_line( @data, :converters => [ :integer,
179
+ :float ] ) )
180
+
181
+ assert_equal( ["Numbers", :integer, 1, :float, 3.015],
182
+ FasterCSV.parse_line( @data, :converters => [ :numeric,
183
+ @custom ] ) )
184
+ end
185
+
186
+ def test_unconverted_fields
187
+ [ [ @data,
188
+ ["Numbers", :integer, 1, :float, 3.015],
189
+ %w{Numbers :integer 1 :float 3.015} ],
190
+ ["\n", Array.new, Array.new] ].each do |test, fields, unconverted|
191
+ row = nil
192
+ assert_nothing_raised(Exception) do
193
+ row = FasterCSV.parse_line( test,
194
+ :converters => [:numeric, @custom],
195
+ :unconverted_fields => true )
196
+ end
197
+ assert_not_nil(row)
198
+ assert_equal(fields, row)
199
+ assert_respond_to(row, :unconverted_fields)
200
+ assert_equal(unconverted, row.unconverted_fields)
201
+ end
202
+
203
+ data = <<-END_CSV.gsub(/^\s+/, "")
204
+ first,second,third
205
+ 1,2,3
206
+ END_CSV
207
+ row = nil
208
+ assert_nothing_raised(Exception) do
209
+ row = FasterCSV.parse_line( data,
210
+ :converters => :numeric,
211
+ :unconverted_fields => true,
212
+ :headers => :first_row )
213
+ end
214
+ assert_not_nil(row)
215
+ assert_equal([["first", 1], ["second", 2], ["third", 3]], row.to_a)
216
+ assert_respond_to(row, :unconverted_fields)
217
+ assert_equal(%w{1 2 3}, row.unconverted_fields)
218
+
219
+ assert_nothing_raised(Exception) do
220
+ row = FasterCSV.parse_line( data,
221
+ :converters => :numeric,
222
+ :unconverted_fields => true,
223
+ :headers => :first_row,
224
+ :return_headers => true )
225
+ end
226
+ assert_not_nil(row)
227
+ assert_equal( [%w{first first}, %w{second second}, %w{third third}],
228
+ row.to_a )
229
+ assert_respond_to(row, :unconverted_fields)
230
+ assert_equal(%w{first second third}, row.unconverted_fields)
231
+
232
+ assert_nothing_raised(Exception) do
233
+ row = FasterCSV.parse_line( data,
234
+ :converters => :numeric,
235
+ :unconverted_fields => true,
236
+ :headers => :first_row,
237
+ :return_headers => true,
238
+ :header_converters => :symbol )
239
+ end
240
+ assert_not_nil(row)
241
+ assert_equal( [[:first, "first"], [:second, "second"], [:third, "third"]],
242
+ row.to_a )
243
+ assert_respond_to(row, :unconverted_fields)
244
+ assert_equal(%w{first second third}, row.unconverted_fields)
245
+
246
+ assert_nothing_raised(Exception) do
247
+ row = FasterCSV.parse_line( data,
248
+ :converters => :numeric,
249
+ :unconverted_fields => true,
250
+ :headers => %w{my new headers},
251
+ :return_headers => true,
252
+ :header_converters => :symbol )
253
+ end
254
+ assert_not_nil(row)
255
+ assert_equal( [[:my, "my"], [:new, "new"], [:headers, "headers"]],
256
+ row.to_a )
257
+ assert_respond_to(row, :unconverted_fields)
258
+ assert_equal(Array.new, row.unconverted_fields)
259
+ end
260
+ end