Skip to content

Instantly share code, notes, and snippets.

@raggi
Created May 9, 2012 19:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raggi/2648240 to your computer and use it in GitHub Desktop.
Save raggi/2648240 to your computer and use it in GitHub Desktop.
csv generation, conversion, with utf16le - example
require 'iconv' unless "".respond_to?(:encode)
require 'active_support/basic_object'
# = EncodeIo
#
# This is essentially a basic delegation class. The reason it isn't
# using the delegate library from stdlib is that FasterCSV has special
# casing for io classes. In order to keep using this, we need #kind_of?
# and #class to pretend that this is the original class.
#
# This class will wrap any IO like object and pass any writes through
# Iconv before passing them on to the underlying IO.
#
# XXX - #putc may be bugged for big endian encodings.
class WF::EncodeIo < ActiveSupport::BasicObject
def initialize(io, from = 'UTF-8', to = 'UTF-16le//IGNORE', encode_opts = {})
@io = io
@from, @to = from, to
@encode_opts = encode_opts
if "".respond_to?(:encode)
@from = Encoding.find(from) unless from.kind_of?(Encoding)
unless to.kind_of?(Encoding)
coding, ignore = to.split('//')
@encode_opts[:fallback] = lambda { |c| c } if ignore =~ /ignore/i
@to = Encoding.find(coding)
end
end
end
conversion_methods = %w[ write syswrite write_nonblock << putc print ]
conversion_methods.each do |m|
class_eval <<-RUBY, __FILE__, __LINE__
def #{m}(*args)
@io.send __method__, *_convert(*args)
end
RUBY
end
def puts(*args)
# The documented IO#puts conventions claim to do this, however, in many
# cases it's actually "optimized", that is, reimplemented inline in C.
args.each do |arg|
line = arg.end_with?($/) ? arg : arg + $/
print line
end
nil
end
def method_missing(name, *args, &block)
@io.send(name, *args, &block)
end
private
if "".respond_to?(:encode)
def _convert(*args)
args.map { |a| a.encode(@to, @from, @encode_opts) }
end
else
def _convert(*args)
args.map { |a| Iconv.conv(@to, @from, a) }
end
end
end
class WF::CSV
UTF16LE_BOM = "\xFF\xFE"
attr_reader :name, :path
def initialize name = 'export.csv.zip'
@name = name
Tempfile.open(@name) do |io|
begin
@path = io.path
ensure
@path = nil
end
end
end
def write
unless @path
raise ArgumentError,
"you must write to the CSV from within the block passed to initialize"
end
Zip::ZipOutputStream.open(@path) do |zip|
zip.put_next_entry(@name)
zip.print(UTF16LE_BOM)
csv = FasterCSV.new(zip, :col_sep => "\t", :row_sep => "\n")
yield csv
end
end
end
# Example usage:
WF::CSV.new("#{CGI.escape page_name}.csv.zip") do |zip|
zip.data do |csv|
csv << %w[header1 header2 header3]
Model.scope.find_each do |model|
csv << model.attributes.values_at(*%w[attr1 attr2 attr3])
end
end
Mailer.send_csv_email user_email, :attachment => zip.path
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment