Class: VFCSV

Inherits:
Object
  • Object
show all
Defined in:
lib/vfcsv.rb,
lib/vfcsv/row.rb,
lib/vfcsv/table.rb,
lib/vfcsv/version.rb

Overview

VFCSV - Very Fast CSV Parser

Drop-in replacement for Ruby's CSV library with SIMD acceleration. Provides 2-20x faster parsing while maintaining full API compatibility.

Examples:

Basic usage (drop-in replacement)

# Instead of: require 'csv'
require 'vfcsv'

# Use VFCSV exactly like CSV
VFCSV.parse("a,b,c\n1,2,3")
VFCSV.read("data.csv", headers: true)
VFCSV.foreach("data.csv") { |row| puts row }

Defined Under Namespace

Modules: RustExt Classes: Generator, Instance, MalformedCSVError, Row, Table, Writer

Constant Summary collapse

Converters =

Built-in converters matching Ruby's CSV

{
  integer: ->(value) {
    begin
      Integer(value, 10)
    rescue ArgumentError, TypeError
      value
    end
  },
  float: ->(value) {
    begin
      Float(value)
    rescue ArgumentError, TypeError
      value
    end
  },
  numeric: ->(value) {
    begin
      Integer(value, 10)
    rescue ArgumentError, TypeError
      begin
        Float(value)
      rescue ArgumentError, TypeError
        value
      end
    end
  },
  date: ->(value) {
    begin
      Date.parse(value)
    rescue ArgumentError, TypeError
      value
    end
  },
  date_time: ->(value) {
    begin
      DateTime.parse(value)
    rescue ArgumentError, TypeError
      value
    end
  },
  all: ->(value) {
    # Try numeric first, then date_time
    result = begin
      Integer(value, 10)
    rescue ArgumentError, TypeError
      begin
        Float(value)
      rescue ArgumentError, TypeError
        begin
          DateTime.parse(value)
        rescue ArgumentError, TypeError
          value
        end
      end
    end
    result
  }
}.freeze
HeaderConverters =

Built-in header converters matching Ruby's CSV

{
  downcase: ->(header) { header.downcase },
  symbol: ->(header) {
    header.encode(Encoding::UTF_8)
          .downcase
          .gsub(/\s+/, "_")
          .gsub(/[^\w]/, "")
          .to_sym
  }
}.freeze
DEFAULT_OPTIONS =

Default options matching Ruby's CSV

{
  col_sep: ",",
  row_sep: :auto,
  quote_char: '"',
  field_size_limit: nil,
  converters: nil,
  unconverted_fields: nil,
  headers: false,
  return_headers: false,
  header_converters: nil,
  skip_blanks: false,
  skip_lines: nil,
  force_quotes: false,
  liberal_parsing: false,
  quote_empty: true,
  nil_value: nil,
  empty_value: "",
}.freeze
VERSION =
"1.0.0"

Class Method Summary collapse

Class Method Details

.filter(input = $stdin, output = $stdout, **options) ⇒ Object

Filter CSV input to output (compatibility method)



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/vfcsv.rb', line 362

def filter(input = $stdin, output = $stdout, **options)
  # Read from input, transform, write to output
  input_str = input.respond_to?(:read) ? input.read : input.to_s
  rows = parse(input_str, **options)

  result = if block_given?
    rows.map { |row| yield row }
  else
    rows
  end

  output_str = generate(**options) do |csv|
    result.each { |row| csv << row if row }
  end

  if output.respond_to?(:write)
    output.write(output_str)
  end

  output_str
end

.foreach(path, mode = "r", **options) {|Array<String>| ... } ⇒ Enumerator

Iterate over a CSV file row by row

Examples:

VFCSV.foreach("data.csv") { |row| puts row.inspect }
VFCSV.foreach("data.csv", headers: true) { |row| puts row["name"] }

Parameters:

  • path (String)

    Path to CSV file

  • mode (String) (defaults to: "r")

    File open mode (ignored, for compatibility)

  • options (Hash)

    Parsing options

Yields:

  • (Array<String>)

    or [Row] Each row

Returns:

  • (Enumerator)

    if no block given



256
257
258
259
260
# File 'lib/vfcsv.rb', line 256

def foreach(path, mode = "r", **options, &block)
  return to_enum(__method__, path, mode, **options) unless block_given?

  parse(File.read(path), **options, &block)
end

.generate(str = nil, **options) {|VFCSV| ... } ⇒ String

Generate CSV string from data

Examples:

VFCSV.generate do |csv|
  csv << ["a", "b", "c"]
  csv << [1, 2, 3]
end
#=> "a,b,c\n1,2,3\n"

Parameters:

  • str (String, nil) (defaults to: nil)

    Optional string to append to

  • options (Hash)

    Generation options

Yields:

  • (VFCSV)

    CSV generator

Returns:

  • (String)

    Generated CSV



276
277
278
279
280
281
# File 'lib/vfcsv.rb', line 276

def generate(str = nil, **options)
  opts = DEFAULT_OPTIONS.merge(options)
  generator = Generator.new(str || "", opts)
  yield generator if block_given?
  generator.to_s
end

.generate_line(row, **options) ⇒ String

Generate a single CSV line

Examples:

VFCSV.generate_line(["a", "b", "c"])
#=> "a,b,c\n"

Parameters:

  • row (Array)

    Fields to generate

  • options (Hash)

    Generation options

Returns:

  • (String)

    CSV line



293
294
295
296
# File 'lib/vfcsv.rb', line 293

def generate_line(row, **options)
  opts = DEFAULT_OPTIONS.merge(options)
  Generator.generate_line(row, **opts)
end

.generate_lines(rows, **options) ⇒ String

Generate multiple CSV lines

Parameters:

  • rows (Array<Array>)

    Rows to generate

  • options (Hash)

    Generation options

Returns:

  • (String)

    CSV string



304
305
306
307
308
# File 'lib/vfcsv.rb', line 304

def generate_lines(rows, **options)
  generate(**options) do |csv|
    rows.each { |row| csv << row }
  end
end

.instance(data = nil, **options) ⇒ VFCSV::Instance

Get or create a CSV instance (for compatibility)

Parameters:

  • data (String, IO) (defaults to: nil)

    CSV data source

  • options (Hash)

    CSV options

Returns:



357
358
359
# File 'lib/vfcsv.rb', line 357

def instance(data = nil, **options)
  Instance.new(data, **options)
end

.open(path, mode = "r", **options) {|VFCSV| ... } ⇒ Object

Open a CSV file for reading or writing

Parameters:

  • path (String)

    Path to CSV file

  • mode (String) (defaults to: "r")

    File open mode ("r", "w", "a", etc.)

  • options (Hash)

    CSV options

Yields:

  • (VFCSV)

    CSV instance

Returns:

  • (Object)

    Result of block, or VFCSV instance



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/vfcsv.rb', line 328

def open(path, mode = "r", **options, &block)
  if mode.include?("w") || mode.include?("a")
    # Writing mode
    csv = Writer.new(path, mode, options)
    if block_given?
      begin
        yield csv
      ensure
        csv.close
      end
    else
      csv
    end
  else
    # Reading mode - just use foreach
    if block_given?
      foreach(path, mode, **options, &block)
    else
      read(path, **options)
    end
  end
end

.parse(str, **options, &block) ⇒ Array<Array<String>>

Parse a CSV string into an array of arrays (or Table if headers: true)

Examples:

Parse simple CSV

VFCSV.parse("a,b,c\n1,2,3")
#=> [["a", "b", "c"], ["1", "2", "3"]]

Parse with headers

VFCSV.parse("a,b,c\n1,2,3", headers: true)
#=> #<VFCSV::Table>

Parameters:

  • str (String)

    CSV data to parse

  • options (Hash)

    Parsing options

Options Hash (**options):

  • :col_sep (String)

    Column separator (default: ",")

  • :quote_char (String)

    Quote character (default: '"')

  • :headers (Boolean)

    Treat first row as headers (default: false)

  • :converters (Symbol, Array, Proc)

    Value converters

  • :header_converters (Symbol, Array, Proc)

    Header converters

  • :skip_blanks (Boolean)

    Skip blank rows (default: false)

  • :skip_lines (Regexp)

    Skip lines matching pattern

Returns:

  • (Array<Array<String>>)

    or [Table] if headers: true



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/vfcsv.rb', line 137

def parse(str, **options, &block)
  opts = DEFAULT_OPTIONS.merge(options)
  rows = rust_ext.parse(str.to_s, opts[:col_sep].to_s, opts[:quote_char].to_s)

  # Post-process: convert empty strings to nil (matching Ruby CSV behavior)
  # Also handle blank rows (single empty field -> empty array)
  nil_value = opts[:nil_value]
  rows = rows.map do |row|
    # A row with just one empty field is a blank row
    if row.size == 1 && row[0].empty?
      []
    else
      row.map { |field| field.empty? ? nil_value : field }
    end
  end

  # Handle skip_blanks
  if opts[:skip_blanks]
    rows = rows.reject { |row| row.empty? || row.all?(&:nil?) }
  end

  # Handle skip_lines
  if opts[:skip_lines]
    pattern = opts[:skip_lines]
    original_str_lines = str.to_s.lines
    rows = rows.reject.with_index do |_row, i|
      i < original_str_lines.length && original_str_lines[i].match?(pattern)
    end
  end

  if opts[:headers] && rows.length > 0
    header_row = rows.shift

    # Apply header converters
    header_row = apply_header_converters(header_row, opts[:header_converters])

    # Build table of Row objects
    table_rows = rows.map do |row|
      # Apply converters to values
      converted_row = apply_converters(row, opts[:converters])
      Row.new(header_row, converted_row)
    end

    result = Table.new(table_rows, headers: header_row)

    if block_given?
      result.each(&block)
      nil
    else
      result
    end
  else
    # Apply converters to all rows
    if opts[:converters]
      rows = rows.map { |row| apply_converters(row, opts[:converters]) }
    end

    if block_given?
      rows.each(&block)
      nil
    else
      rows
    end
  end
end

.parse_line(line, **options) ⇒ Array<String>

Parse a single CSV line

Examples:

VFCSV.parse_line("a,b,c")
#=> ["a", "b", "c"]

Parameters:

  • line (String)

    Single CSV line

  • options (Hash)

    Parsing options

Returns:

  • (Array<String>)

    Fields from the line



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/vfcsv.rb', line 213

def parse_line(line, **options)
  opts = DEFAULT_OPTIONS.merge(options)
  rows = rust_ext.parse(line.to_s, opts[:col_sep].to_s, opts[:quote_char].to_s)
  row = rows.first || []

  if opts[:converters]
    row = apply_converters(row, opts[:converters])
  end

  row
end

.read(path, **options) ⇒ Array<Array<String>>

Read a CSV file

Examples:

VFCSV.read("data.csv")
VFCSV.read("data.csv", headers: true)

Parameters:

  • path (String)

    Path to CSV file

  • options (Hash)

    Parsing options (same as parse)

Returns:

  • (Array<Array<String>>)

    or [Table] if headers: true



235
236
237
# File 'lib/vfcsv.rb', line 235

def read(path, **options)
  parse(File.read(path), **options)
end

.readlines(path, **options) ⇒ Object

Alias for read



240
241
242
# File 'lib/vfcsv.rb', line 240

def readlines(path, **options)
  read(path, **options)
end

.simd_infoObject

Get SIMD information



385
386
387
# File 'lib/vfcsv.rb', line 385

def simd_info
  rust_ext.simd_info
end

.table(path, **options) ⇒ Table

Read CSV as a table with headers

Parameters:

  • path (String)

    Path to CSV file

  • options (Hash)

    Parsing options

Returns:

  • (Table)

    Table object



316
317
318
# File 'lib/vfcsv.rb', line 316

def table(path, **options)
  read(path, headers: true, **options)
end