CodeSampleX

Exemple

csv 3.3.2: Handle custom converter registration in CSV::Converters with CSV::FieldInfo metadata, non-String converter short-circuiting, skip_lines filtering, and CSV.filter stream transformation in CSV 3.3.2

Échantillon vérifié pour gem csv 3.3.2: Handle custom converter registration in CSV::Converters with CSV::FieldInfo metadata, non-String converter…

sha256:8a8545c284e4585374983e887167cea95e9e8ba4e7e067eb4b7dd081d598e40f

Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré. Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes. MIT-0

Preuves d'exécution

L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.

Base de preuve
Contrat signé réussi
Reçus de vérification
2
Clés de signature qui l’ont compilé
2
Environnement déclaré ruby linux x64 ruby ruby rubygems

Environnements des exécutions de vérification

Environnement Contrat Étapes Exécution
ruby 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · rubygems@1
2026-08-17
ruby 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · rubygems@1
2026-08-18

Cas

HOW
Objectif
Handle custom converter registration in CSV::Converters with CSV::FieldInfo metadata, non-String converter short-circuiting, skip_lines filtering, and CSV.filter stream transformation in CSV 3.3.2
Paquets
Symboles
  • CSV.parse
  • CSV.filter
  • CSV::Converters
  • CSV::FieldInfo
  • CSV::Row
Environnement
ruby
Créé
2026-08-17T14:41:39Z

Contrat

  1. CSV::FieldInfo provides 0-based column index, 1-based logical line number, and transformed header symbol metadata to custom converters registered in CSV::Converters.
  2. CSV converter pipelines execute sequentially for each field, short-circuiting downstream converters as soon as any converter returns a non-String value.
  3. CSV.parse with skip_lines pattern filters comments prior to headers and between rows while maintaining accurate 1-based logical line tracking.
  4. CSV.filter streams data transformations across IO objects, yielding header arrays first when write_headers is true and CSV::Row objects for data lines.
  5. CSV::Row supports appending header-value pairs or nil-header fields, query presence with header? and field?, and column deletion returning key-value pairs.

Fichiers

  • Gemfile
  • Gemfile.lock
  • NOTES.md
  • csx.json
  • test/contract.rb

Télécharger l’artefact source (tar.gz)

Code source

Gemfile
source "https://rubygems.org"

gem "csv", "3.3.2"
Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    csv (3.3.2)

PLATFORMS
  ruby
  x64-mingw-ucrt

DEPENDENCIES
  csv (= 3.3.2)

CHECKSUMS
  bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac
  csv (3.3.2) sha256=6ff0c135e65e485d1864dde6c1703b60d34cc9e19bed8452834a0b28a519bd4e

BUNDLED WITH
  4.0.18
NOTES.md
# Notes

### What `search_known_solution` answered

`search_known_solution` returned existing published samples:
- `sha256:cd77a19583a5e0694a988590f8ce51c6b9a0a08226060f6d68e94be73eacc130` (covering `:time` converter, `CSV.open` with `StringIO`, `CSV::TSV`, and custom `col_sep` with blank lines).
- `sha256:8ff4e3da4a710e341dcf90db0b99e9af669dfab68342fc4011bfefa30d684a1b` (covering `CSV.parse` returning `CSV::Table`, column indexing, `table.to_a`, and `CSV.generate` with `write_headers: true`).
- `sha256:3d12db62adbd2a7953f464fa2bbaaffa6d87ba0e99054b30dad1404f2b997473` (covering `headers: true` combined with `return_headers: true` column prepending).
- `sha256:bd2f2439031898f70ac4d8c0be64eb855887b222f9e161cb86cb4feeca7ebddb` (covering `liberal_parsing: {backslash_quote: true}`, `CSV::Row#to_h` duplicate header resolution, `strip: true`, and `quote_empty: true` roundtripping).
- `sha256:6c5ffc8a79396c5e831eed2edeefc0ee240d543d3937dad970365842c019aef5` (covering `CSV::InvalidEncodingError` inheritance hierarchy, `ArgumentError` on nil inputs, and `liberal_parsing: true` unclosed quote boundaries).
- `sha256:da583d8f8e0b640a9c899c67cd5cee4ed2a94c48cb31e90d6df9dbf2df098cdd` (covering unquoted empty field nil return values when `nil_value` is unset).

This sample tests a distinct set of standard behaviors in `csv` 3.3.2: custom converter registration via `CSV::Converters`, positional and header metadata exposed through `CSV::FieldInfo`, non-`String` converter short-circuiting in sequential pipelines, pattern filtering via `skip_lines` with logical row numbering, streaming I/O transformations using `CSV.filter`, and `CSV::Row` structural mutation methods.

### Exact Release Pinned

`csv` version `3.3.2` pinned in `Gemfile` and `Gemfile.lock`.

### Observed Behaviour

1. **Custom Converter Registration and `CSV::FieldInfo` Metadata**:
   - Registering a converter block into `CSV::Converters[:custom_name]` makes it available to pass by symbol in the `converters:` option array.
   - When a converter accepts a second block parameter (`|value, field_info|`), `field_info` provides:
     - `field_info.index`: 0-based column index within the parsed row.
     - `field_info.line`: 1-based logical line index in the parsed sequence.
     - `field_info.header`: Transformed header identifier (affected by `header_converters`).

2. **Converter Chaining and Non-String Short-Circuiting**:
   - Converters in `converters: [...]` execute sequentially from left to right for each field.
   - If a converter returns a modified `String`, downstream string converters receive the transformed string.
   - When any converter returns a non-`String` value (such as `Integer`, `Float`, `Time`, or custom objects), `CSV` halts further conversion for that field, skipping subsequent string converters.

3. **`skip_lines` Pattern Filtering and Logical Row Numbering**:
   - `skip_lines: Regexp` / `skip_lines: String` drops matched comment lines before the header row and between data rows.
   - Logical row numbers reported in `CSV::FieldInfo#line` track parsed rows (row 1 is the header row, row 2 is the first data row, row 3 is the second data row).

4. **`CSV.filter` Stream Transformation**:
   - `CSV.filter(input_io, output_io, ...)` streams rows between IO objects.
   - When `headers: true, write_headers: true` is enabled, the first iteration yields the header row as a mutable `Array`, while subsequent iterations yield data rows as `CSV::Row` objects.
   - In-place row mutations within the block are formatted and written to the output stream.

5. **`CSV::Row` Mutation and Query Semantics**:
   - `row << [key, value]` appends a column header and value pair.
   - `row << value` appends a field with a `nil` header.
   - `row.delete(key)` removes the column and returns `[key, value]`.
   - `row.header?(key)` and `row.field?(value)` test header and value presence respectively.
csx.json
{"case":{"caseId":"case:sha256:05721fdb2a4ef0c9e4ed9ec62f74f8ccfa0ca27f7f107ab304faa1085d172d08","contract":["CSV::FieldInfo provides 0-based column index, 1-based logical line number, and transformed header symbol metadata to custom converters registered in CSV::Converters.","CSV converter pipelines execute sequentially for each field, short-circuiting downstream converters as soon as any converter returns a non-String value.","CSV.parse with skip_lines pattern filters comments prior to headers and between rows while maintaining accurate 1-based logical line tracking.","CSV.filter streams data transformations across IO objects, yielding header arrays first when write_headers is true and CSV::Row objects for data lines.","CSV::Row supports appending header-value pairs or nil-header fields, query presence with header? and field?, and column deletion returning key-value pairs."],"goal":"Handle custom converter registration in CSV::Converters with CSV::FieldInfo metadata, non-String converter short-circuiting, skip_lines filtering, and CSV.filter stream transformation in CSV 3.3.2","kind":"HOW","packages":["pkg:gem/csv@3.3.2"],"schemaVersion":1,"symbols":["CSV.parse","CSV.filter","CSV::Converters","CSV::FieldInfo","CSV::Row"]},"contractCommand":["ruby","test/contract.rb"],"environment":{"arch":"x64","ecosystem":"gem","executionContext":"ruby","language":"ruby","os":"linux","packageManager":"rubygems","runtime":"ruby","schemaVersion":1},"license":"MIT-0","packages":["pkg:gem/csv@3.3.2"],"schemaVersion":1,"symbols":["CSV.parse","CSV.filter","CSV::Converters","CSV::FieldInfo","CSV::Row"],"verifierAdapter":"rubygems@1"}
test/contract.rb
require "csv"
require "stringio"

# 1. Custom converter registration in CSV::Converters and CSV::FieldInfo metadata
CSV::Converters[:strip_currency] = lambda do |value, field_info|
  if field_info && field_info.header == :price && value.is_a?(String)
    (value.delete("$").to_f * 100).round
  else
    value
  end
end

metadata_entries = []
metadata_logger = lambda do |value, field_info|
  metadata_entries << {
    value: value,
    index: field_info.index,
    line: field_info.line,
    header: field_info.header
  }
  value
end

csv_input = "item,price,qty\nApple,$1.25,10\nBanana,$0.80,25\n"
table = CSV.parse(
  csv_input,
  headers: true,
  header_converters: :symbol,
  converters: [metadata_logger, :strip_currency, :integer]
)

# Custom registered converter transformed currency string to integer cents
raise "Price conversion mismatch" unless table[:price] == [125, 80]
raise "Integer converter mismatch" unless table[:qty] == [10, 25]
raise "Item string preserved mismatch" unless table[:item] == ["Apple", "Banana"]

# FieldInfo metadata accuracy: index (0-based column), line (1-based logical parsed row), header (symbol from header_converters)
raise "Expected 6 metadata records" unless metadata_entries.size == 6
raise "First field metadata mismatch" unless metadata_entries[0] == { value: "Apple", index: 0, line: 2, header: :item }
raise "Second field metadata mismatch" unless metadata_entries[1] == { value: "$1.25", index: 1, line: 2, header: :price }
raise "Third field metadata mismatch" unless metadata_entries[2] == { value: "10", index: 2, line: 2, header: :qty }
raise "Fourth field metadata mismatch" unless metadata_entries[3] == { value: "Banana", index: 0, line: 3, header: :item }
raise "Fifth field metadata mismatch" unless metadata_entries[4] == { value: "$0.80", index: 1, line: 3, header: :price }
raise "Sixth field metadata mismatch" unless metadata_entries[5] == { value: "25", index: 2, line: 3, header: :qty }

# 2. Converter chaining and non-String short-circuiting
c_uppercase = ->(v) { v.is_a?(String) ? v.upcase : v }
c_to_int = ->(v) { Integer(v) rescue v }
c_downstream_calls = []
c_downstream = lambda do |v|
  c_downstream_calls << v
  v
end

chain_parsed = CSV.parse("100,hello\n", converters: [c_to_int, c_uppercase, c_downstream])

# "100" was converted to Integer(100) by c_to_int, so subsequent String converters are short-circuited
raise "Integer conversion mismatch in chain" unless chain_parsed[0][0] == 100
raise "String conversion mismatch in chain" unless chain_parsed[0][1] == "HELLO"
raise "Downstream short-circuit mismatch" unless c_downstream_calls == ["HELLO"]

# 3. skip_lines filtering with header detection and FieldInfo logical line tracking
commented_data = "# Comment header 1\n# Comment header 2\nname,score\n# Interstitial comment\nAlice,100\nBob,95\n"
field_lines = []
field_tracker = lambda do |val, info|
  field_lines << { val: val, line: info.line, index: info.index, header: info.header }
  val
end

parsed_skip = CSV.parse(commented_data, headers: true, skip_lines: /^#/, converters: [field_tracker])
raise "Skip lines row count mismatch" unless parsed_skip.size == 2
raise "Skip lines table mapping mismatch" unless parsed_skip.map(&:to_h) == [{ "name" => "Alice", "score" => "100" }, { "name" => "Bob", "score" => "95" }]

# Logical line numbers in FieldInfo: row 1 is header (name,score), row 2 is Alice, row 3 is Bob
alice_info = field_lines.find { |r| r[:val] == "Alice" }
bob_info = field_lines.find { |r| r[:val] == "Bob" }
raise "Alice line tracking mismatch" unless alice_info[:line] == 2 && alice_info[:index] == 0
raise "Bob line tracking mismatch" unless bob_info[:line] == 3 && bob_info[:index] == 0

# 4. CSV.filter IO stream transformation
in_io = StringIO.new("id,name,score\n1,Alice,80\n2,Bob,45\n")
out_io = StringIO.new

row_classes = []
CSV.filter(in_io, out_io, headers: true, write_headers: true, converters: :integer) do |row|
  row_classes << row.class
  if row.is_a?(Array)
    row << "grade"
  else
    grade = row["score"] >= 70 ? "PASS" : "FAIL"
    row << ["grade", grade]
  end
end

raise "CSV.filter yielded row classes mismatch" unless row_classes == [Array, CSV::Row, CSV::Row]

out_io.rewind
expected_stream = "id,name,score,grade\n1,Alice,80,PASS\n2,Bob,45,FAIL\n"
raise "CSV.filter output stream mismatch" unless out_io.read == expected_stream

# 5. CSV::Row mutation and query methods
row_obj = CSV::Row.new(["k1", "k2"], ["v1", "v2"])
row_obj << ["k3", "v3"]
row_obj << "v4"

raise "Row headers mismatch" unless row_obj.headers == ["k1", "k2", "k3", nil]
raise "Row fields mismatch" unless row_obj.fields == ["v1", "v2", "v3", "v4"]
raise "Row header? positive mismatch" unless row_obj.header?("k1") == true
raise "Row header? negative mismatch" unless row_obj.header?("missing") == false
raise "Row field? positive mismatch" unless row_obj.field?("v2") == true
raise "Row field? negative mismatch" unless row_obj.field?("missing") == false

deleted_pair = row_obj.delete("k2")
raise "Row delete return value mismatch" unless deleted_pair == ["k2", "v2"]
raise "Row state after delete mismatch" unless row_obj.to_h == { "k1" => "v1", "k3" => "v3", nil => "v4" }

Seeder d'origine

csx-seed