* lib/csv.rb: Remove the dangerous serialization feature.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@39077 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
3221e7d29c
commit
b614d7823c
@ -1,3 +1,7 @@
|
|||||||
|
Wed Feb 6 03:27:19 2013 James Edward Gray II <james@graysoftinc.com>
|
||||||
|
|
||||||
|
* lib/csv.rb: Remove the dangerous serialization feature.
|
||||||
|
|
||||||
Wed Feb 6 00:56:00 2013 Zachary Scott <zachary@zacharyscott.net>
|
Wed Feb 6 00:56:00 2013 Zachary Scott <zachary@zacharyscott.net>
|
||||||
|
|
||||||
* lib/irb.rb: Remove example from restrictions, it works [Github #246]
|
* lib/irb.rb: Remove example from restrictions, it works [Github #246]
|
||||||
|
127
lib/csv.rb
127
lib/csv.rb
@ -1050,133 +1050,6 @@ class CSV
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# This method allows you to serialize an Array of Ruby objects to a String or
|
|
||||||
# File of CSV data. This is not as powerful as Marshal or YAML, but perhaps
|
|
||||||
# useful for spreadsheet and database interaction.
|
|
||||||
#
|
|
||||||
# Out of the box, this method is intended to work with simple data objects or
|
|
||||||
# Structs. It will serialize a list of instance variables and/or
|
|
||||||
# Struct.members().
|
|
||||||
#
|
|
||||||
# If you need need more complicated serialization, you can control the process
|
|
||||||
# by adding methods to the class to be serialized.
|
|
||||||
#
|
|
||||||
# A class method csv_meta() is responsible for returning the first row of the
|
|
||||||
# document (as an Array). This row is considered to be a Hash of the form
|
|
||||||
# key_1,value_1,key_2,value_2,... CSV::load() expects to find a class key
|
|
||||||
# with a value of the stringified class name and CSV::dump() will create this,
|
|
||||||
# if you do not define this method. This method is only called on the first
|
|
||||||
# object of the Array.
|
|
||||||
#
|
|
||||||
# The next method you can provide is an instance method called csv_headers().
|
|
||||||
# This method is expected to return the second line of the document (again as
|
|
||||||
# an Array), which is to be used to give each column a header. By default,
|
|
||||||
# CSV::load() will set an instance variable if the field header starts with an
|
|
||||||
# @ character or call send() passing the header as the method name and
|
|
||||||
# the field value as an argument. This method is only called on the first
|
|
||||||
# object of the Array.
|
|
||||||
#
|
|
||||||
# Finally, you can provide an instance method called csv_dump(), which will
|
|
||||||
# be passed the headers. This should return an Array of fields that can be
|
|
||||||
# serialized for this object. This method is called once for every object in
|
|
||||||
# the Array.
|
|
||||||
#
|
|
||||||
# The +io+ parameter can be used to serialize to a File, and +options+ can be
|
|
||||||
# anything CSV::new() accepts.
|
|
||||||
#
|
|
||||||
def self.dump(ary_of_objs, io = "", options = Hash.new)
|
|
||||||
obj_template = ary_of_objs.first
|
|
||||||
|
|
||||||
csv = new(io, options)
|
|
||||||
|
|
||||||
# write meta information
|
|
||||||
begin
|
|
||||||
csv << obj_template.class.csv_meta
|
|
||||||
rescue NoMethodError
|
|
||||||
csv << [:class, obj_template.class]
|
|
||||||
end
|
|
||||||
|
|
||||||
# write headers
|
|
||||||
begin
|
|
||||||
headers = obj_template.csv_headers
|
|
||||||
rescue NoMethodError
|
|
||||||
headers = obj_template.instance_variables.sort
|
|
||||||
if obj_template.class.ancestors.find { |cls| cls.to_s =~ /\AStruct\b/ }
|
|
||||||
headers += obj_template.members.map { |mem| "#{mem}=" }.sort
|
|
||||||
end
|
|
||||||
end
|
|
||||||
csv << headers
|
|
||||||
|
|
||||||
# serialize each object
|
|
||||||
ary_of_objs.each do |obj|
|
|
||||||
begin
|
|
||||||
csv << obj.csv_dump(headers)
|
|
||||||
rescue NoMethodError
|
|
||||||
csv << headers.map do |var|
|
|
||||||
if var[0] == ?@
|
|
||||||
obj.instance_variable_get(var)
|
|
||||||
else
|
|
||||||
obj[var[0..-2]]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if io.is_a? String
|
|
||||||
csv.string
|
|
||||||
else
|
|
||||||
csv.close
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# This method is the reading counterpart to CSV::dump(). See that method for
|
|
||||||
# a detailed description of the process.
|
|
||||||
#
|
|
||||||
# You can customize loading by adding a class method called csv_load() which
|
|
||||||
# will be passed a Hash of meta information, an Array of headers, and an Array
|
|
||||||
# of fields for the object the method is expected to return.
|
|
||||||
#
|
|
||||||
# Remember that all fields will be Strings after this load. If you need
|
|
||||||
# something else, use +options+ to setup converters or provide a custom
|
|
||||||
# csv_load() implementation.
|
|
||||||
#
|
|
||||||
def self.load(io_or_str, options = Hash.new)
|
|
||||||
csv = new(io_or_str, options)
|
|
||||||
|
|
||||||
# load meta information
|
|
||||||
meta = Hash[*csv.shift]
|
|
||||||
cls = meta["class".encode(csv.encoding)].split("::".encode(csv.encoding)).
|
|
||||||
inject(Object) do |c, const|
|
|
||||||
c.const_get(const)
|
|
||||||
end
|
|
||||||
|
|
||||||
# load headers
|
|
||||||
headers = csv.shift
|
|
||||||
|
|
||||||
# unserialize each object stored in the file
|
|
||||||
results = csv.inject(Array.new) do |all, row|
|
|
||||||
begin
|
|
||||||
obj = cls.csv_load(meta, headers, row)
|
|
||||||
rescue NoMethodError
|
|
||||||
obj = cls.allocate
|
|
||||||
headers.zip(row) do |name, value|
|
|
||||||
if name[0] == ?@
|
|
||||||
obj.instance_variable_set(name, value)
|
|
||||||
else
|
|
||||||
obj.send(name, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
all << obj
|
|
||||||
end
|
|
||||||
|
|
||||||
csv.close unless io_or_str.is_a? String
|
|
||||||
|
|
||||||
results
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# :call-seq:
|
# :call-seq:
|
||||||
# filter( options = Hash.new ) { |row| ... }
|
# filter( options = Hash.new ) { |row| ... }
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
#!/usr/bin/env ruby -w
|
|
||||||
# encoding: UTF-8
|
|
||||||
|
|
||||||
# tc_serialization.rb
|
|
||||||
#
|
|
||||||
# Created by James Edward Gray II on 2005-10-31.
|
|
||||||
# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
|
|
||||||
# under the terms of Ruby's license.
|
|
||||||
|
|
||||||
require_relative "base"
|
|
||||||
require "tempfile"
|
|
||||||
|
|
||||||
# An example of how to provide custom CSV serialization.
|
|
||||||
class Hash
|
|
||||||
def self.csv_load( meta, headers, fields )
|
|
||||||
self[*headers.zip(fields).to_a.flatten.map { |e| eval(e) }]
|
|
||||||
end
|
|
||||||
|
|
||||||
def csv_headers
|
|
||||||
keys.map { |key| key.inspect }
|
|
||||||
end
|
|
||||||
|
|
||||||
def csv_dump( headers )
|
|
||||||
headers.map { |header| fetch(eval(header)).inspect }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TestCSV::Serialization < TestCSV
|
|
||||||
extend DifferentOFS
|
|
||||||
|
|
||||||
### Classes Used to Test Serialization ###
|
|
||||||
|
|
||||||
class ReadOnlyName
|
|
||||||
def initialize( first, last )
|
|
||||||
@first, @last = first, last
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :first, :last
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
%w{first last}.all? { |att| send(att) == other.send(att) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Name = Struct.new(:first, :last)
|
|
||||||
|
|
||||||
class FullName < Name
|
|
||||||
def initialize( first, last, suffix = nil )
|
|
||||||
super(first, last)
|
|
||||||
|
|
||||||
@suffix = suffix
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_accessor :suffix
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
%w{first last suffix}.all? { |att| send(att) == other.send(att) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
### Tests ###
|
|
||||||
|
|
||||||
def test_class_dump
|
|
||||||
@names = [ %w{James Gray},
|
|
||||||
%w{Dana Gray},
|
|
||||||
%w{Greg Brown} ].map do |first, last|
|
|
||||||
ReadOnlyName.new(first, last)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_nothing_raised(Exception) do
|
|
||||||
@data = CSV.dump(@names)
|
|
||||||
end
|
|
||||||
assert_equal(<<-END_CLASS_DUMP.gsub(/^\s*/, ""), @data)
|
|
||||||
class,TestCSV::Serialization::ReadOnlyName
|
|
||||||
@first,@last
|
|
||||||
James,Gray
|
|
||||||
Dana,Gray
|
|
||||||
Greg,Brown
|
|
||||||
END_CLASS_DUMP
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_struct_dump
|
|
||||||
@names = [ %w{James Gray},
|
|
||||||
%w{Dana Gray},
|
|
||||||
%w{Greg Brown} ].map do |first, last|
|
|
||||||
Name.new(first, last)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_nothing_raised(Exception) do
|
|
||||||
@data = CSV.dump(@names)
|
|
||||||
end
|
|
||||||
assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
|
|
||||||
class,TestCSV::Serialization::Name
|
|
||||||
first=,last=
|
|
||||||
James,Gray
|
|
||||||
Dana,Gray
|
|
||||||
Greg,Brown
|
|
||||||
END_STRUCT_DUMP
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_inherited_struct_dump
|
|
||||||
@names = [ %w{James Gray II},
|
|
||||||
%w{Dana Gray},
|
|
||||||
%w{Greg Brown} ].map do |first, last, suffix|
|
|
||||||
FullName.new(first, last, suffix)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_nothing_raised(Exception) do
|
|
||||||
@data = CSV.dump(@names)
|
|
||||||
end
|
|
||||||
assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
|
|
||||||
class,TestCSV::Serialization::FullName
|
|
||||||
@suffix,first=,last=
|
|
||||||
II,James,Gray
|
|
||||||
,Dana,Gray
|
|
||||||
,Greg,Brown
|
|
||||||
END_STRUCT_DUMP
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_load
|
|
||||||
%w{ test_class_dump
|
|
||||||
test_struct_dump
|
|
||||||
test_inherited_struct_dump }.each do |test|
|
|
||||||
send(test)
|
|
||||||
CSV.load(@data).each do |loaded|
|
|
||||||
assert_instance_of(@names.first.class, loaded)
|
|
||||||
assert_equal(@names.shift, loaded)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_io
|
|
||||||
test_class_dump
|
|
||||||
|
|
||||||
tempfile = Tempfile.new(%w"serialization .csv")
|
|
||||||
tempfile.close
|
|
||||||
data_file = tempfile.path
|
|
||||||
CSV.dump(@names, File.open(data_file, "wb"))
|
|
||||||
|
|
||||||
assert(File.exist?(data_file))
|
|
||||||
assert_equal(<<-END_IO_DUMP.gsub(/^\s*/, ""), File.read(data_file))
|
|
||||||
class,TestCSV::Serialization::ReadOnlyName
|
|
||||||
@first,@last
|
|
||||||
James,Gray
|
|
||||||
Dana,Gray
|
|
||||||
Greg,Brown
|
|
||||||
END_IO_DUMP
|
|
||||||
|
|
||||||
assert_equal(@names, CSV.load(File.open(data_file)))
|
|
||||||
|
|
||||||
tempfile.close(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_dump_and_load
|
|
||||||
obj = {1 => "simple", test: Hash}
|
|
||||||
assert_equal(obj, CSV.load(CSV.dump([obj])).first)
|
|
||||||
end
|
|
||||||
end
|
|
@ -17,5 +17,4 @@ require "test_data_converters"
|
|||||||
require "test_row"
|
require "test_row"
|
||||||
require "test_table"
|
require "test_table"
|
||||||
require "test_headers"
|
require "test_headers"
|
||||||
require "test_serialization"
|
|
||||||
require "test_encodings"
|
require "test_encodings"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user