Class: SmarterCSV::Writer

Inherits:
Object
  • Object
show all
Includes:
Options
Defined in:
lib/smarter_csv/writer.rb,
lib/smarter_csv/writer_options.rb

Defined Under Namespace

Modules: Options

Constant Summary

Constants included from Options

Options::DEFAULT_OPTIONS

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_path_or_io, given_options = {}) ⇒ Writer

Returns a new instance of Writer.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/smarter_csv/writer.rb', line 64

def initialize(file_path_or_io, given_options = {})
  opts = Options::DEFAULT_OPTIONS.merge(given_options)
  @options = opts

  @row_sep = opts[:row_sep]
  @col_sep = opts[:col_sep]
  @quote_char = opts[:quote_char]
  @escaped_quote_char = @quote_char * 2
  @force_quotes = opts[:force_quotes] == true
  @quote_headers = opts[:quote_headers] == true
  @disable_auto_quoting = opts[:disable_auto_quoting] == true
  @value_converters = opts[:value_converters] || {}
  @encoding = opts[:encoding]
  @write_nil_value = opts[:write_nil_value]
  @write_empty_value = opts[:write_empty_value]
  @write_bom = opts[:write_bom] == true
  @write_headers = opts[:write_headers] == true
  @map_all_keys = @value_converters.has_key?(:_all)
  @mapped_keys = Set.new(@value_converters.keys - [:_all])
  @header_converter = opts[:header_converter]

  if given_options.has_key?(:discover_headers)
    @discover_headers = given_options[:discover_headers] == true
  else
    @discover_headers = !(given_options.has_key?(:map_headers) || given_options.has_key?(:headers))
  end

  @headers = opts[:headers].dup
  @headers = given_options[:map_headers].keys if given_options.has_key?(:map_headers) && !given_options.has_key?(:headers)
  @map_headers = opts[:map_headers]

  # Accept an IO-like object (StringIO, IO, etc.) or any path-like object (String, Pathname, etc.)
  if file_path_or_io.respond_to?(:write)
    # External IO handed in — we should not close it ourselves.
    @output_file = file_path_or_io
    @file_opened_by_us = false
  else
    path =
      if file_path_or_io.respond_to?(:to_path)
        file_path_or_io.to_path
      elsif file_path_or_io.is_a?(String)
        file_path_or_io
      else
        raise ArgumentError,
              "SmarterCSV::Writer expects an IO-like object (responding to #write) " \
              "or a path-like object (responding to #to_path or being a String), " \
              "but got #{file_path_or_io.class}"
      end
    mode = @encoding ? "w+:#{@encoding}" : 'w+'
    @output_file = File.open(path, mode)
    @file_opened_by_us = true
  end
  @quote_regex = Regexp.union(@col_sep, @row_sep, @quote_char)

  if !@discover_headers && !@headers.empty?
    # Headers are fully known at construction time — write the header line immediately
    # and stream data rows directly to @output_file, bypassing the temp file entirely.
    @temp_file = nil
    @output_file.write("\xEF\xBB\xBF") if @write_bom
    write_header_line if @write_headers
  else
    @temp_file = Tempfile.new('smarter_csv')
  end
end

Class Method Details

.default_optionsObject



60
61
62
# File 'lib/smarter_csv/writer.rb', line 60

def self.default_options
  Options::DEFAULT_OPTIONS
end

Instance Method Details

#<<(data) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/smarter_csv/writer.rb', line 129

def <<(data)
  case data
  when Hash
    process_hash(data)
  when Array
    data.each { |item| self << item }
  when NilClass
    # ignore
  else
    raise InvalidInputData, "Invalid data type: #{data.class}. Must be a Hash or an Array."
  end
end

#finalizeObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/smarter_csv/writer.rb', line 142

def finalize
  if @temp_file
    # Header-discovery mode: headers were accumulated while writing rows;
    # now prepend the header line and copy the buffered rows to the output.
    @output_file.write("\xEF\xBB\xBF") if @write_bom
    write_header_line if @write_headers
    @temp_file.rewind
    @output_file.write(@temp_file.read)
    @temp_file.close!
  end
  # In direct-write mode (@temp_file == nil) the header line and all data rows
  # were already written to @output_file — nothing left to do but flush and close.
  @output_file.flush
  @output_file.close if @file_opened_by_us # only close files we opened; caller owns external IO objects
end