ruby-changes:47321
From: glass <ko1@a...>
Date: Fri, 28 Jul 2017 16:46:27 +0900 (JST)
Subject: [ruby-changes:47321] glass:r59437 (trunk): csv.rb: fix incompatibility introduced in r59428
glass 2017-07-28 16:46:20 +0900 (Fri, 28 Jul 2017) New Revision: 59437 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=59437 Log: csv.rb: fix incompatibility introduced in r59428 * lib/csv.rb: fix incompatibility introduced in r59428. CSV.new takes options as keyword arguments. * test/csv/test_features.rb: add a test to ensure it raises error againt unknown options * test/csv/test_features.rb: add a test to ensure row_sep option is properly applied Modified files: trunk/lib/csv.rb trunk/test/csv/test_features.rb trunk/test/csv/test_interface.rb Index: lib/csv.rb =================================================================== --- lib/csv.rb (revision 59436) +++ lib/csv.rb (revision 59437) @@ -1066,7 +1066,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1066 # fetch or create the instance for this signature @@instances ||= Hash.new - instance = (@@instances[sig] ||= new(data, options)) + instance = (@@instances[sig] ||= new(data, options)) if block_given? yield instance # run block, if given, returning result @@ -1099,7 +1099,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1099 # The <tt>:output_row_sep</tt> +option+ defaults to # <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>). # - def self.filter(input, output=nil, **options) + def self.filter(input=nil, output=nil, **options) # parse options for input, output, or both in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR} options.each do |key, value| @@ -1192,7 +1192,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1192 # (<tt>$/</tt>) when calling this method. # def self.generate_line(row, encoding: nil, **options) - options[:row_sep] ||= $INPUT_RECORD_SEPARATOR + options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options) str = String.new if encoding str.force_encoding(encoding) @@ -1268,14 +1268,14 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1268 def self.open(*args, **options) # wrap a File opened with the remaining +args+ with no newline # decorator - file_opts = options.dup - file_opts[:universal_newline] ||= false + file_opts = {universal_newline: false}.merge(options) + begin f = File.open(*args, file_opts) rescue ArgumentError => e raise unless /needs binmode/ =~ e.message and args.size == 1 args << "rb" - file_opts[:encoding] ||= Encoding.default_external + file_opts = {encoding: Encoding.default_external}.merge(file_opts) retry end begin @@ -1518,20 +1518,19 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1518 # Options cannot be overridden in the instance methods for performance reasons, # so be sure to set what you want here. # - def initialize(data, internal_encoding: nil, encoding: nil, **options) - if data.nil? - raise ArgumentError.new("Cannot parse nil as CSV") - end - - # build the options for this read/write - options = DEFAULT_OPTIONS.merge(options) + def initialize(data, col_sep: ",", row_sep: :auto, quote_char: '"', field_size_limit: nil, + converters: nil, unconverted_fields: nil, headers: false, return_headers: false, + write_headers: nil, header_converters: nil, skip_blanks: false, force_quotes: false, + skip_lines: nil, liberal_parsing: false, internal_encoding: nil, external_encoding: nil, encoding: nil) + raise ArgumentError.new("Cannot parse nil as CSV") if data.nil? # create the IO object we will read from - @io = data.is_a?(String) ? StringIO.new(data) : data + @io = data.is_a?(String) ? StringIO.new(data) : data # honor the IO encoding if we can, otherwise default to ASCII-8BIT internal_encoding = Encoding.find(internal_encoding) if internal_encoding + external_encoding = Encoding.find(external_encoding) if external_encoding if encoding - encoding, = encoding.split(":") if encoding.is_a?(String) + encoding, = encoding.split(":", 2) if encoding.is_a?(String) encoding = Encoding.find(encoding) end @encoding = raw_encoding(nil) || internal_encoding || encoding || @@ -1540,14 +1539,23 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1539 # prepare for building safe regular expressions in the target encoding, # if we can transcode the needed characters # - @re_esc = "\\".encode(@encoding).freeze rescue "" - @re_chars = /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/ + @re_esc = "\\".encode(@encoding).freeze rescue "" + @re_chars = /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/ + @unconverted_fields = unconverted_fields - init_separators(options) - init_parsers(options) - init_converters(options) - init_headers(options) - init_comments(options) + # Stores header row settings and loads header converters, if needed. + @use_headers = headers + @return_headers = return_headers + @write_headers = write_headers + + # headers must be delayed until shift(), in case they need a row of content + @headers = nil + + init_separators(col_sep, row_sep, quote_char, force_quotes) + init_parsers(skip_blanks, field_size_limit, liberal_parsing) + init_converters(converters, :@converters, :convert) + init_converters(header_converters, :@header_converters, :header_convert) + init_comments(skip_lines) @force_encoding = !!encoding @@ -1994,7 +2002,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2002 # # This method also establishes the quoting rules used for CSV output. # - def init_separators(col_sep: nil, row_sep: nil, quote_char: nil, force_quotes: nil, **options) + def init_separators(col_sep, row_sep, quote_char, force_quotes) # store the selected separators @col_sep = col_sep.to_s.encode(@encoding) @row_sep = row_sep # encode after resolving :auto @@ -2092,7 +2100,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2100 end # Pre-compiles parsers and stores them by name for access during reads. - def init_parsers(skip_blanks: nil, field_size_limit: nil, liberal_parsing: nil, **options) + def init_parsers(skip_blanks, field_size_limit, liberal_parsing) # store the parser behaviors @skip_blanks = skip_blanks @field_size_limit = field_size_limit @@ -2124,45 +2132,23 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2132 # The <tt>:unconverted_fields</tt> option is also activated for # <tt>:converters</tt> calls, if requested. # - def init_converters(options, field_name = :converters) - if field_name == :converters - @unconverted_fields = options.delete(:unconverted_fields) - end - - instance_variable_set("@#{field_name}", Array.new) - - # find the correct method to add the converters - convert = method(field_name.to_s.sub(/ers\Z/, "")) + def init_converters(converters, ivar_name, convert_method) + converters = case converters + when nil then [] + when Array then converters + else [converters] + end + instance_variable_set(ivar_name, []) + convert = method(convert_method) # load converters - unless options[field_name].nil? - # allow a single converter not wrapped in an Array - unless options[field_name].is_a? Array - options[field_name] = [options[field_name]] - end - # load each converter... - options[field_name].each do |converter| - if converter.is_a? Proc # custom code block - convert.call(&converter) - else # by name - convert.call(converter) - end + converters.each do |converter| + if converter.is_a? Proc # custom code block + convert.call(&converter) + else # by name + convert.call(converter) end end - - options.delete(field_name) - end - - # Stores header row settings and loads header converters, if needed. - def init_headers(headers: nil, return_headers: nil, write_headers: nil, **options) - @use_headers = headers - @return_headers = return_headers - @write_headers = write_headers - - # headers must be delayed until shift(), in case they need a row of content - @headers = nil - - init_converters(options, :header_converters) end # Stores the pattern of comments to skip from the provided options. @@ -2171,7 +2157,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2157 # Strings are converted to a Regexp. # # See also CSV.new - def init_comments(skip_lines: nil, **options) + def init_comments(skip_lines) @skip_lines = skip_lines @skip_lines = Regexp.new(@skip_lines) if @skip_lines.is_a? String if @skip_lines and not @skip_lines.respond_to?(:match) Index: test/csv/test_features.rb =================================================================== --- test/csv/test_features.rb (revision 59436) +++ test/csv/test_features.rb (revision 59437) @@ -137,6 +137,15 @@ class TestCSV::Features < TestCSV https://github.com/ruby/ruby/blob/trunk/test/csv/test_features.rb#L137 test_lineno end + def test_unknown_options + assert_raise_with_message(ArgumentError, /unknown keyword/) { + CSV.new(@sample_data, unknown: :error) + } + assert_raise_with_message(ArgumentError, /unknown keyword/) { + CSV.new(@sample_data, universal_newline: true) + } + end + def test_skip_blanks assert_equal(4, @csv.to_a.size) Index: test/csv/test_interface.rb =================================================================== --- test/csv/test_interface.rb (revision 59436) +++ test/csv/test_interface.rb (revision 59437) @@ -166,6 +166,9 @@ class TestCSV::Interface < TestCSV https://github.com/ruby/ruby/blob/trunk/test/csv/test_interface.rb#L166 assert_not_nil(line) assert_instance_of(String, line) assert_equal("1;2;3\n", line) + + line = CSV.generate_line(%w"1 2", row_sep: nil) + assert_equal("1,2", line) end def test_write_header_detection -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/