ruby-changes:47313
From: glass <ko1@a...>
Date: Thu, 27 Jul 2017 18:54:05 +0900 (JST)
Subject: [ruby-changes:47313] glass:r59428 (trunk): csv.rb: use keyword parameters
glass 2017-07-27 18:53:58 +0900 (Thu, 27 Jul 2017) New Revision: 59428 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=59428 Log: csv.rb: use keyword parameters * lib/csv.rb: usb keyword parameters to receive options * test/csv/test_features.rb: remove a test for checking options Modified files: trunk/lib/csv.rb trunk/test/csv/test_features.rb Index: lib/csv.rb =================================================================== --- lib/csv.rb (revision 59427) +++ lib/csv.rb (revision 59428) @@ -540,7 +540,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L540 # # csv_row.fields.to_csv( options ) # - def to_csv(options = Hash.new) + def to_csv(**options) fields.to_csv(options) end alias_method :to_s, :to_csv @@ -894,9 +894,8 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L894 # This method assumes you want the Table.headers(), unless you explicitly # pass <tt>:write_headers => false</tt>. # - def to_csv(options = Hash.new) - wh = options.fetch(:write_headers, true) - @table.inject(wh ? [headers.to_csv(options)] : [ ]) do |rows, row| + def to_csv(write_headers: true, **options) + @table.inject(write_headers ? [headers.to_csv(options)] : [ ]) do |rows, row| if row.header_row? rows else @@ -1060,7 +1059,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1059 # If a block is given, the instance is passed to the block and the return # value becomes the return value of the block. # - def self.instance(data = $stdout, options = Hash.new) + def self.instance(data = $stdout, **options) # create a _signature_ for this method call, data object and options sig = [data.object_id] + options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s }) @@ -1078,9 +1077,9 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1077 # # :call-seq: - # filter( options = Hash.new ) { |row| ... } - # filter( input, options = Hash.new ) { |row| ... } - # filter( input, output, options = Hash.new ) { |row| ... } + # filter( **options ) { |row| ... } + # filter( input, **options ) { |row| ... } + # filter( input, output, **options ) { |row| ... } # # This method is a convenience for building Unix-like filters for CSV data. # Each row is yielded to the provided block which can alter it as needed. @@ -1100,25 +1099,23 @@ 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(*args) + def self.filter(input, output=nil, **options) # parse options for input, output, or both in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR} - if args.last.is_a? Hash - args.pop.each do |key, value| - case key.to_s - when /\Ain(?:put)?_(.+)\Z/ - in_options[$1.to_sym] = value - when /\Aout(?:put)?_(.+)\Z/ - out_options[$1.to_sym] = value - else - in_options[key] = value - out_options[key] = value - end + options.each do |key, value| + case key.to_s + when /\Ain(?:put)?_(.+)\Z/ + in_options[$1.to_sym] = value + when /\Aout(?:put)?_(.+)\Z/ + out_options[$1.to_sym] = value + else + in_options[key] = value + out_options[key] = value end end # build input and output wrappers - input = new(args.shift || ARGF, in_options) - output = new(args.shift || $stdout, out_options) + input = new(input || ARGF, in_options) + output = new(output || $stdout, out_options) # read, yield, write input.each do |row| @@ -1141,7 +1138,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1138 # <tt>encoding: "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file # but transcode it to UTF-8 before CSV parses it. # - def self.foreach(path, options = Hash.new, &block) + def self.foreach(path, **options, &block) return to_enum(__method__, path, options) unless block open(path, options) do |csv| csv.each(&block) @@ -1150,8 +1147,8 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1147 # # :call-seq: - # generate( str, options = Hash.new ) { |csv| ... } - # generate( options = Hash.new ) { |csv| ... } + # generate( str, **options ) { |csv| ... } + # generate( **options ) { |csv| ... } # # This method wraps a String you provide, or an empty default String, in a # CSV object which is passed to the provided block. You can use the block to @@ -1166,19 +1163,17 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1163 # String to set the base Encoding for the output. CSV needs this hint if you # plan to output non-ASCII compatible data. # - def self.generate(*args) + def self.generate(str=nil, **options) # add a default empty String, if none was given - if args.first.is_a? String - io = StringIO.new(args.shift) + if str + io = StringIO.new(str) io.seek(0, IO::SEEK_END) - args.unshift(io) else - encoding = args[-1][:encoding] if args.last.is_a?(Hash) + encoding = options[:encoding] str = String.new str.force_encoding(encoding) if encoding - args.unshift(str) end - csv = new(*args) # wrap + csv = new(str, options) # wrap yield csv # yield for appending csv.string # return final String end @@ -1196,10 +1191,9 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1191 # The <tt>:row_sep</tt> +option+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt> # (<tt>$/</tt>) when calling this method. # - def self.generate_line(row, options = Hash.new) - options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options) - encoding = options.delete(:encoding) - str = String.new + def self.generate_line(row, encoding: nil, **options) + options[:row_sep] ||= $INPUT_RECORD_SEPARATOR + str = String.new if encoding str.force_encoding(encoding) elsif field = row.find { |f| not f.nil? } @@ -1210,10 +1204,10 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1204 # # :call-seq: - # open( filename, mode = "rb", options = Hash.new ) { |faster_csv| ... } - # open( filename, options = Hash.new ) { |faster_csv| ... } - # open( filename, mode = "rb", options = Hash.new ) - # open( filename, options = Hash.new ) + # open( filename, mode = "rb", **options ) { |faster_csv| ... } + # open( filename, **options ) { |faster_csv| ... } + # open( filename, mode = "rb", **options ) + # open( filename, **options ) # # This method opens an IO object, and wraps that with CSV. This is intended # as the primary interface for writing a CSV file. @@ -1271,18 +1265,17 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1265 # * truncate() # * tty?() # - def self.open(*args) - # find the +options+ Hash - options = if args.last.is_a? Hash then args.pop else Hash.new end + def self.open(*args, **options) # wrap a File opened with the remaining +args+ with no newline # decorator - file_opts = {universal_newline: false}.merge(options) + file_opts = options.dup + file_opts[:universal_newline] ||= false 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}.merge(file_opts) + file_opts[:encoding] ||= Encoding.default_external retry end begin @@ -1306,14 +1299,14 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1299 # # :call-seq: - # parse( str, options = Hash.new ) { |row| ... } - # parse( str, options = Hash.new ) + # parse( str, **options ) { |row| ... } + # parse( str, **options ) # # This method can be used to easily parse CSV out of a String. You may either # provide a +block+ which will be called with each row of the String in turn, # or just use the returned Array of Arrays (when no +block+ is given). # - # You pass your +str+ to read from, and an optional +options+ Hash containing + # You pass your +str+ to read from, and an optional +options+ containing # anything CSV::new() understands. # def self.parse(*args, &block) @@ -1336,7 +1329,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1329 # # The +options+ parameter can be anything CSV::new() understands. # - def self.parse_line(line, options = Hash.new) + def self.parse_line(line, **options) new(line, options).shift end @@ -1367,7 +1360,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1360 # converters: :numeric, # header_converters: :symbol }.merge(options) ) # - def self.table(path, options = Hash.new) + def self.table(path, **options) read( path, { headers: true, converters: :numeric, header_converters: :symbol }.merge(options) ) @@ -1525,7 +1518,7 @@ 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, options = Hash.new) + def initialize(data, internal_encoding: nil, encoding: nil, **options) if data.nil? raise ArgumentError.new("Cannot parse nil as CSV") end @@ -1536,17 +1529,12 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1529 # create the IO object we will read from @io = data.is_a?(String) ? StringIO.new(data) : data # honor the IO encoding if we can, otherwise default to ASCII-8BIT - @encoding = raw_encoding(nil) || - ( if encoding = options.delete(:internal_encoding) - case encoding - when Encoding; encoding - else Encoding.find(encoding) - end - end ) || - ( case encoding = options.delete(:encoding) - when Encoding; encoding - when /\A[^:]+/; Encoding.find($&) - end ) || + internal_encoding = Encoding.find(internal_encoding) if internal_encoding + if encoding + encoding, = encoding.split(":") if encoding.is_a?(String) + encoding = Encoding.find(encoding) + end + @encoding = raw_encoding(nil) || internal_encoding || encoding || Encoding.default_internal || Encoding.default_external # # prepare for building safe regular expressions in the target encoding, @@ -1561,12 +1549,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1549 init_headers(options) init_comments(options) - @force_encoding = !!(encoding || options.delete(:encoding)) - options.delete(:internal_encoding) - options.delete(:external_encoding) - unless options.empty? - raise ArgumentError, "Unknown options: #{options.keys.join(', ')}." - end + @force_encoding = !!encoding # track our own lineno since IO gets confused about line-ends is CSV fields @lineno = 0 @@ -2011,11 +1994,11 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1994 # # This method also establishes the quoting rules used for CSV output. # - def init_separators(options) + def init_separators(col_sep: nil, row_sep: nil, quote_char: nil, force_quotes: nil, **options) # store the selected separators - @col_sep = options.delete(:col_sep).to_s.encode(@encoding) - @row_sep = options.delete(:row_sep) # encode after resolving :auto - @quote_char = options.delete(:quote_char).to_s.encode(@encoding) + @col_sep = col_sep.to_s.encode(@encoding) + @row_sep = row_sep # encode after resolving :auto + @quote_char = quote_char.to_s.encode(@encoding) @double_quote_char = @quote_char * 2 if @quote_char.length != 1 @@ -2081,13 +2064,11 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2064 @row_sep = @row_sep.to_s.encode(@encoding) # establish quoting rules - @force_quotes = options.delete(:force_quotes) - do_quote = lambda do |field| - field = String(field) + @force_quotes = force_quotes + do_quote = lambda do |field| + field = String(field) encoded_quote = @quote_char.encode(field.encoding) - encoded_quote + - field.gsub(encoded_quote, encoded_quote * 2) + - encoded_quote + encoded_quote + field.gsub(encoded_quote, encoded_quote * 2) + encoded_quote end quotable_chars = encode_str("\r\n", @col_sep, @quote_char) @quote = if @force_quotes @@ -2111,11 +2092,11 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2092 end # Pre-compiles parsers and stores them by name for access during reads. - def init_parsers(options) + def init_parsers(skip_blanks: nil, field_size_limit: nil, liberal_parsing: nil, **options) # store the parser behaviors - @skip_blanks = options.delete(:skip_blanks) - @field_size_limit = options.delete(:field_size_limit) - @liberal_parsing = options.delete(:liberal_parsing) + @skip_blanks = skip_blanks + @field_size_limit = field_size_limit + @liberal_parsing = liberal_parsing # prebuild Regexps for faster parsing esc_row_sep = escape_re(@row_sep) @@ -2173,10 +2154,10 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2154 end # Stores header row settings and loads header converters, if needed. - def init_headers(options) - @use_headers = options.delete(:headers) - @return_headers = options.delete(:return_headers) - @write_headers = options.delete(:write_headers) + 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 @@ -2190,8 +2171,8 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2171 # Strings are converted to a Regexp. # # See also CSV.new - def init_comments(options) - @skip_lines = options.delete(:skip_lines) + def init_comments(skip_lines: nil, **options) + @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) raise ArgumentError, ":skip_lines has to respond to matches" @@ -2371,7 +2352,7 @@ class Array # :nodoc: https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2352 # # ["CSV", "data"].to_csv # #=> "CSV,data\n" - def to_csv(options = Hash.new) + def to_csv(**options) CSV.generate_line(self, options) end end @@ -2381,7 +2362,7 @@ class String # :nodoc: https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2362 # # "CSV,data".parse_csv # #=> ["CSV", "data"] - def parse_csv(options = Hash.new) + def parse_csv(**options) CSV.parse_line(self, options) end end Index: test/csv/test_features.rb =================================================================== --- test/csv/test_features.rb (revision 59427) +++ test/csv/test_features.rb (revision 59428) @@ -137,12 +137,6 @@ 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/) { - CSV.new(@sample_data, unknown: :error) - } - end - def test_skip_blanks assert_equal(4, @csv.to_a.size) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/