Class: VFCSV
- Inherits:
-
Object
- Object
- VFCSV
- 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.
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
-
.filter(input = $stdin, output = $stdout, **options) ⇒ Object
Filter CSV input to output (compatibility method).
-
.foreach(path, mode = "r", **options) {|Array<String>| ... } ⇒ Enumerator
Iterate over a CSV file row by row.
-
.generate(str = nil, **options) {|VFCSV| ... } ⇒ String
Generate CSV string from data.
-
.generate_line(row, **options) ⇒ String
Generate a single CSV line.
-
.generate_lines(rows, **options) ⇒ String
Generate multiple CSV lines.
-
.instance(data = nil, **options) ⇒ VFCSV::Instance
Get or create a CSV instance (for compatibility).
-
.open(path, mode = "r", **options) {|VFCSV| ... } ⇒ Object
Open a CSV file for reading or writing.
-
.parse(str, **options, &block) ⇒ Array<Array<String>>
Parse a CSV string into an array of arrays (or Table if headers: true).
-
.parse_line(line, **options) ⇒ Array<String>
Parse a single CSV line.
-
.read(path, **options) ⇒ Array<Array<String>>
Read a CSV file.
-
.readlines(path, **options) ⇒ Object
Alias for read.
-
.simd_info ⇒ Object
Get SIMD information.
-
.table(path, **options) ⇒ Table
Read CSV as a table with headers.
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, **) # Read from input, transform, write to output input_str = input.respond_to?(:read) ? input.read : input.to_s rows = parse(input_str, **) result = if block_given? rows.map { |row| yield row } else rows end output_str = generate(**) 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
256 257 258 259 260 |
# File 'lib/vfcsv.rb', line 256 def foreach(path, mode = "r", **, &block) return to_enum(__method__, path, mode, **) unless block_given? parse(File.read(path), **, &block) end |
.generate(str = nil, **options) {|VFCSV| ... } ⇒ String
Generate CSV string from data
276 277 278 279 280 281 |
# File 'lib/vfcsv.rb', line 276 def generate(str = nil, **) opts = DEFAULT_OPTIONS.merge() generator = Generator.new(str || "", opts) yield generator if block_given? generator.to_s end |
.generate_line(row, **options) ⇒ String
Generate a single CSV line
293 294 295 296 |
# File 'lib/vfcsv.rb', line 293 def generate_line(row, **) opts = DEFAULT_OPTIONS.merge() Generator.generate_line(row, **opts) end |
.generate_lines(rows, **options) ⇒ String
Generate multiple CSV lines
304 305 306 307 308 |
# File 'lib/vfcsv.rb', line 304 def generate_lines(rows, **) generate(**) do |csv| rows.each { |row| csv << row } end end |
.instance(data = nil, **options) ⇒ VFCSV::Instance
Get or create a CSV instance (for compatibility)
357 358 359 |
# File 'lib/vfcsv.rb', line 357 def instance(data = nil, **) Instance.new(data, **) end |
.open(path, mode = "r", **options) {|VFCSV| ... } ⇒ Object
Open a CSV file for reading or writing
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", **, &block) if mode.include?("w") || mode.include?("a") # Writing mode csv = Writer.new(path, mode, ) 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, **, &block) else read(path, **) end end end |
.parse(str, **options, &block) ⇒ Array<Array<String>>
Parse a CSV string into an array of arrays (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, **, &block) opts = DEFAULT_OPTIONS.merge() 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
213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/vfcsv.rb', line 213 def parse_line(line, **) opts = DEFAULT_OPTIONS.merge() 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
235 236 237 |
# File 'lib/vfcsv.rb', line 235 def read(path, **) parse(File.read(path), **) end |
.readlines(path, **options) ⇒ Object
Alias for read
240 241 242 |
# File 'lib/vfcsv.rb', line 240 def readlines(path, **) read(path, **) end |
.simd_info ⇒ Object
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
316 317 318 |
# File 'lib/vfcsv.rb', line 316 def table(path, **) read(path, headers: true, **) end |