From 6dee0fab9b72b9c185fa742685f6486de9373243 Mon Sep 17 00:00:00 2001 From: nahi Date: Wed, 26 May 2004 14:30:30 +0000 Subject: * lib/csv.rb (CSV.read, CSV.readlines): added. works as IO.read and IO.readlines in CSV format. * lib/csv.rb (CSV.parse): [CAUTION] behavior changed. in the past, CSV.parse accepts a filename to be read-opened (it was just a shortcut of CSV.open(filename, 'r')). now CSV.parse accepts a string or a stream to be parsed e.g. CSV.parse("1,2\n3,r") #=> [['1', '2'], ['3', '4']] * test/csv/test_csv.rb: follow above changes. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@6410 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 13 +++++ lib/csv.rb | 145 +++++++++++++++++++++++++++++---------------------- test/csv/test_csv.rb | 78 ++++++++++++++++----------- 3 files changed, 144 insertions(+), 92 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6d6b47790b..ea1215332f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Wed May 26 23:12:13 2004 NAKAMURA, Hiroshi + + * lib/csv.rb (CSV.read, CSV.readlines): added. works as IO.read and + IO.readlines in CSV format. + + * lib/csv.rb (CSV.parse): [CAUTION] behavior changed. in the past, + CSV.parse accepts a filename to be read-opened (it was just a + shortcut of CSV.open(filename, 'r')). now CSV.parse accepts a + string or a stream to be parsed e.g. + CSV.parse("1,2\n3,r") #=> [['1', '2'], ['3', '4']] + + * test/csv/test_csv.rb: follow above changes. + Wed May 26 14:19:42 2004 Nobuyoshi Nakada * eval.c (rb_eval, eval): make line number consistent on eval with diff --git a/lib/csv.rb b/lib/csv.rb index 26fc6435eb..0f6907be57 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -11,24 +11,17 @@ class CSV class IllegalFormatError < RuntimeError; end - def CSV.open(path, mode, fs = nil, rs = nil, &block) - if mode == 'r' or mode == 'rb' - open_reader(path, mode, fs, rs, &block) - elsif mode == 'w' or mode == 'wb' - open_writer(path, mode, fs, rs, &block) - else - raise ArgumentError.new("'mode' must be 'r', 'rb', 'w', or 'wb'") - end - end - - def CSV.foreach(path, rs = nil, &block) - open_reader(path, 'r', ',', rs, &block) - end - - # Open a CSV formatted file for reading. + # Open a CSV formatted file for reading or writing. + # + # For reading. # # EXAMPLE 1 - # reader = CSV.parse('csvfile.csv') + # CSV.open('csvfile.csv', 'r') do |row| + # p row + # end + # + # EXAMPLE 2 + # reader = CSV.open('csvfile.csv', 'r') # row1 = reader.shift # row2 = reader.shift # if row2.empty? @@ -36,11 +29,6 @@ class CSV # end # reader.close # - # EXAMPLE 2 - # CSV.parse('csvfile.csv') do |row| - # p row - # end - # # ARGS # filename: filename to parse. # col_sep: Column separator. ?, by default. If you want to separate @@ -51,24 +39,21 @@ class CSV # RETURNS # reader instance. To get parse result, see CSV::Reader#each. # - def CSV.parse(path, fs = nil, rs = nil, &block) - open_reader(path, 'r', fs, rs, &block) - end - - # Open a CSV formatted file for writing. # - # EXAMPLE 1 - # writer = CSV.generate('csvfile.csv') - # writer << ['r1c1', 'r1c2'] << ['r2c1', 'r2c2'] << [nil, nil] - # writer.close + # For writing. # - # EXAMPLE 2 - # CSV.generate('csvfile.csv') do |writer| + # EXAMPLE 1 + # CSV.open('csvfile.csv', 'w') do |writer| # writer << ['r1c1', 'r1c2'] # writer << ['r2c1', 'r2c2'] # writer << [nil, nil] # end # + # EXAMPLE 2 + # writer = CSV.open('csvfile.csv', 'w') + # writer << ['r1c1', 'r1c2'] << ['r2c1', 'r2c2'] << [nil, nil] + # writer.close + # # ARGS # filename: filename to generate. # col_sep: Column separator. ?, by default. If you want to separate @@ -80,10 +65,49 @@ class CSV # writer instance. See CSV::Writer#<< and CSV::Writer#add_row to know how # to generate CSV string. # + def CSV.open(path, mode, fs = nil, rs = nil, &block) + if mode == 'r' or mode == 'rb' + open_reader(path, mode, fs, rs, &block) + elsif mode == 'w' or mode == 'wb' + open_writer(path, mode, fs, rs, &block) + else + raise ArgumentError.new("'mode' must be 'r', 'rb', 'w', or 'wb'") + end + end + + def CSV.foreach(path, rs = nil, &block) + open_reader(path, 'r', ',', rs, &block) + end + + def CSV.read(path, length = nil, offset = nil) + CSV.parse(IO.read(path, length, offset)) + end + + def CSV.readlines(path, rs = nil) + reader = open_reader(path, 'r', ',', rs) + begin + reader.collect { |row| row } + ensure + reader.close + end + end + def CSV.generate(path, fs = nil, rs = nil, &block) open_writer(path, 'w', fs, rs, &block) end + # Parse lines from given string or stream. Return rows as an Array of Arrays. + def CSV.parse(str_or_readable, fs = nil, rs = nil, &block) + if block + CSV::Reader.parse(str_or_readable, fs, rs) do |row| + yield(row) + end + nil + else + CSV::Reader.create(str_or_readable, fs, rs).collect { |row| row } + end + end + # Parse a line from given string. Bear in mind it parses ONE LINE. Rest of # the string is ignored for example "a,b\r\nc,d" => ['a', 'b'] and the # second line 'c,d' is ignored. @@ -157,7 +181,7 @@ class CSV # src[](idx_out_of_bounds) must return nil. A String satisfies this # requirement. # idx: index of parsing location of 'src'. 0 origin. - # out_dev: buffer for parsed cells. Must respond '<<(CSV::Cell)'. + # out_dev: buffer for parsed cells. Must respond '<<(aString)'. # col_sep: Column separator. ?, by default. If you want to separate # fields with semicolon, give ?; here. # row_sep: Row separator. nil by default. nil means "\r\n or \n". If you @@ -200,13 +224,9 @@ class CSV # instead. To generate multi-row CSV string, see EXAMPLE below. # # EXAMPLE - # def d(str) - # CSV::Cell.new(str, false) - # end - # - # row1 = [d('a'), d('b')] - # row2 = [d('c'), d('d')] - # row3 = [d('e'), d('f')] + # row1 = ['a', 'b'] + # row2 = ['c', 'd'] + # row3 = ['e', 'f'] # src = [row1, row2, row3] # buf = '' # src.each do |row| @@ -216,8 +236,8 @@ class CSV # p buf # # ARGS - # src: an Array of CSV::Cell to be converted to CSV string. Must respond to - # 'size' and '[](idx)'. src[idx] must return CSV::Cell. + # src: an Array of String to be converted to CSV string. Must respond to + # 'size' and '[](idx)'. src[idx] must return String. # cells: num of cells in a line. # out_dev: buffer for generated CSV string. Must respond to '<<(string)'. # col_sep: Column separator. ?, by default. If you want to separate @@ -485,13 +505,17 @@ class CSV # Parse CSV data and get lines. Given block is called for each parsed row. # Block value is always nil. Rows are not cached for performance reason. - def Reader.parse(str_or_readable, fs = ',', rs = nil) - reader = create(str_or_readable, fs, rs) - reader.each do |row| - yield(row) + def Reader.parse(str_or_readable, fs = ',', rs = nil, &block) + reader = Reader.create(str_or_readable, fs, rs) + if block + reader.each do |row| + yield(row) + end + reader.close + nil + else + reader end - reader.close - nil end # Returns reader instance. @@ -619,28 +643,23 @@ class CSV # outfile = File.open('csvout', 'wb') # CSV::Writer.generate(outfile) do |csv| # csv << ['c1', nil, '', '"', "\r\n", 'c2'] - # # or - # csv.add_row [ - # CSV::Cell.new('c1', false), - # CSV::Cell.new('dummy', true), - # CSV::Cell.new('', false), - # CSV::Cell.new('"', false), - # CSV::Cell.new("\r\n", false) - # CSV::Cell.new('c2', false) - # ] - # ... # ... # end # # outfile.close # class Writer - # Generate CSV. Given block is called with the writer instance. - def Writer.generate(str_or_writable, fs = ',', rs = nil) + # Given block is called with the writer instance. str_or_writable must + # handle '<<(string)'. + def Writer.generate(str_or_writable, fs = ',', rs = nil, &block) writer = Writer.create(str_or_writable, fs, rs) - yield(writer) - writer.close - nil + if block + yield(writer) + writer.close + nil + else + writer + end end # str_or_writable must handle '<<(string)'. diff --git a/test/csv/test_csv.rb b/test/csv/test_csv.rb index eca88321f0..929d07a786 100644 --- a/test/csv/test_csv.rb +++ b/test/csv/test_csv.rb @@ -2,7 +2,7 @@ require 'test/unit' require 'tempfile' require 'fileutils' -require 'csv' +require '../lib/csv.rb' class CSV class StreamBuf @@ -529,39 +529,40 @@ public end def test_s_parse - reader = CSV.parse(@infile) - assert_instance_of(CSV::IOReader, reader) - reader.close + result = CSV.parse(File.read(@infile)) + assert_instance_of(Array, result) + assert_instance_of(Array, result[0]) - reader = CSV.parse(@infile) - assert_instance_of(CSV::IOReader, reader) - reader.close + result = CSV.parse(File.read(@infile)) + assert_instance_of(Array, result) + assert_instance_of(Array, result[0]) - reader = CSV.parse(@infile, ?;) - assert_instance_of(CSV::IOReader, reader) - reader.close + assert_equal([], CSV.parse("")) + assert_equal([[nil]], CSV.parse("\n")) - CSV.parse(@infile) do |row| + CSV.parse(File.read(@infile)) do |row| assert_instance_of(Array, row) break end - CSV.parse(@infiletsv, ?\t) do |row| + CSV.parse(File.read(@infiletsv), ?\t) do |row| assert_instance_of(Array, row) break end - assert_raises(Errno::ENOENT) do - CSV.parse("NoSuchFileOrDirectory") + CSV.parse("") do |row| + assert(false) end - assert_raises(Errno::ENOENT) do - CSV.parse("NoSuchFileOrDirectory", ?;) + count = 0 + CSV.parse("\n") do |row| + assert_equal([nil], row) + count += 1 end + assert_equal(1, count) - CSV.parse(@emptyfile) do |row| - assert_fail("Must not reach here") - end + assert_equal([["a|b-c|d"]], CSV.parse("a|b-c|d")) + assert_equal([["a", "b"], ["c", "d"]], CSV.parse("a|b-c|d", "|", "-")) end def test_s_open_writer @@ -824,15 +825,13 @@ public assert_equal(col, row) end - row = CSV.parse_line("a,b,c", nil, nil) - assert_equal(['a', 'b', 'c'], row) - - row = CSV.parse_line("a,b,c", nil, ?b) - assert_equal(['a', nil], row) - - row = CSV.parse_line("a,b,c", nil, "c") - assert_equal(['a', 'b', nil], row) - + assert_equal(['a', 'b', 'c'], CSV.parse_line("a,b,c", nil, nil)) + assert_equal(['a', nil], CSV.parse_line("a,b,c", nil, ?b)) + assert_equal(['a', 'b', nil], CSV.parse_line("a,b,c", nil, "c")) + assert_equal([nil], CSV.parse_line("")) + assert_equal([nil], CSV.parse_line("\n")) + assert_equal([""], CSV.parse_line("\"\"\n")) + # Illegal format. buf = [] row = CSV.parse_line("a,b,\"c\"\ra") @@ -1696,6 +1695,10 @@ public assert_equal([nil], reader.shift) assert_equal(['abc'], reader.shift) + reader = CSV::Reader.create("ab\ncdabcef", "abc", "\n") + assert_equal(['ab'], reader.shift) + assert_equal(['cd', "ef"], reader.shift) + # EOF while fs/rs matching reader = CSV::Reader.create("ab", 'ab-', "xyz") assert_equal(['ab'], reader.shift) @@ -1711,7 +1714,7 @@ public assert_equal([nil, ",,", nil], reader.shift) end - def test_foreach + def test_s_foreach File.open(@outfile, "w") do |f| f << "1,2,3\n4,5,6" end @@ -1730,4 +1733,21 @@ public } assert_equal([['1', '2', '3'], ['4', '5', '6']], row) end + + def test_s_readlines + File.open(@outfile, "w") do |f| + f << "1,2,3\n4,5,6" + end + assert_equal([["1", "2", "3"], ["4", "5", "6"]], CSV.readlines(@outfile)) + assert_equal([["1", "2", nil], [nil, "5", "6"]], CSV.readlines(@outfile, "3\n4")) + end + + def test_s_read + File.open(@outfile, "w") do |f| + f << "1,2,3\n4,5,6" + end + assert_equal([["1", "2", "3"], ["4", "5", "6"]], CSV.read(@outfile)) + assert_equal([["1", "2"]], CSV.read(@outfile, 3)) + assert_equal([[nil], ["4", nil]], CSV.read(@outfile, 3, 5)) + end end -- cgit v1.2.3