dsv 0.12.7 → 0.12.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac547ecbc72cfc0cfb3b4e2a1f9c0c5327ea1f4e064c126a28aa85f8c3a42ded
4
- data.tar.gz: 5be7f5303ed207575c43dfacac8d7b4e68f259172b48d25aba60104af57b68f7
3
+ metadata.gz: 190e66c0b25b64c18dac0b955ae59fe64591d14d369cafa950d31b1aa56a2b30
4
+ data.tar.gz: 6a7ea034b8f8da40716efb89a555951307ee312c38142b84c71f2712c57e0622
5
5
  SHA512:
6
- metadata.gz: 022fbe8f4caf5baf90c785ba1ef586574d270331e25b6734e1e982766e1a8836a3182f1f92452734ce1d3316e834e0b07da4b8f9b89f6105c45222e4505dab7b
7
- data.tar.gz: 19cd8f16d5b72349be0e066f63a5dcbac7cc9ea1ab4736e67cd07507415794ce2f9cc47d9bc747358125c252480018b1a0170efd820bdab8056611f46971cfd0
6
+ metadata.gz: e6f6cb3c8f04131f26bf26be33f7dc1e000a4e39a58537f3c327c250837d880477b07e478be8688a57b124dfd85f24f92d569109de15e95ec61b95631652e493
7
+ data.tar.gz: f0787b59e38dbac938fcd7474b815e24d2660c33438289a9720ca239b439a3d7435ce650db9dd5279b2ae2266ccfe763d1dfe8005d92cdb2dd1a8c1369f16cb8
data/CHANGELOG CHANGED
@@ -1,5 +1,17 @@
1
1
  # dsv/CHANGELOG
2
2
 
3
+ ## 20261028
4
+
5
+ 0.12.8: a stray quote no longer takes in the next line.
6
+
7
+ 1. /complete_quoted_row()/row_fields()/: takes the next line into a row while a quoted field in it is open, rather than while its quotes are odd in number. A stray quote had made the count odd, and one beside an open field even.
8
+ 2. ~ reassemble_quoted_fields(), split_row(): nil where a quoted field is still open at the end of the row.
9
+ 3. ~ split_double_quoted(): takes a row as wholly quoted only where it ends in a closing quote.
10
+ 4. + parse_fields(), from parse_row(), so that the read loop splits each row once.
11
+ 5. ~ test/dsv_test.rb: - the skip on a stray quote; + a quoted field across lines after a stray quote.
12
+ 6. ~ lib/DSV/VERSION.rb: /0.12.7/0.12.8/.
13
+
14
+
3
15
  ## 20261027
4
16
 
5
17
  0.12.7: ~ reassemble_quoted_fields(): an escaped quote before a separator no longer closes a quoted field.
data/lib/DSV/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # DSV::VERSION
3
3
 
4
4
  class DSV
5
- VERSION = '0.12.7'
5
+ VERSION = '0.12.8'
6
6
  end
data/lib/dsv.rb CHANGED
@@ -180,7 +180,7 @@ class DSV
180
180
  else
181
181
  read_header
182
182
  @rows = []
183
- @source.each(@row_separator){|raw_row| @rows << parse_row(complete_quoted_row(raw_row), *selected_columns)}
183
+ @source.each(@row_separator){|raw_row| @rows << parse_fields(row_fields(raw_row), *selected_columns)}
184
184
  @rows
185
185
  end
186
186
  end
@@ -211,7 +211,7 @@ class DSV
211
211
  @columns = (
212
212
  if header_row? && ['r', 'r+', 'a+'].include?(@mode) && (first_row = first_row?)
213
213
  columns, i = {}, -1
214
- split_row(first_row).each do |column_name|
214
+ (split_row(first_row) || []).each do |column_name|
215
215
  columns[column_name] = columns.key?(column_name) ? [*columns[column_name], i += 1] : (i += 1)
216
216
  end
217
217
  columns
@@ -263,7 +263,7 @@ class DSV
263
263
  value.nil? || value.empty?
264
264
  end
265
265
 
266
- # One row into its fields, by the quote mode: :none splits; :double splits a row as it promises, wholly quoted or wholly unquoted, and one that is neither as unspecified would; unspecified splits and reassembles a quoted field that held the separator, a doubled quote inside it reading as one. Direct string operations throughout; the scanner-based parser is on the branch scanner-parser.
266
+ # One row into its fields, by the quote mode: :none splits; :double splits a row as it promises, wholly quoted or wholly unquoted, and one that is neither as unspecified would; unspecified splits and reassembles a quoted field that held the separator, a doubled quote inside it reading as one. nil where a quoted field in the row is still open at its end. Direct string operations throughout; the scanner-based parser is on the branch scanner-parser.
267
267
  def split_row(raw_row)
268
268
  row = raw_row.chomp(@row_separator)
269
269
  case @quote
@@ -302,10 +302,10 @@ class DSV
302
302
  end
303
303
  end
304
304
 
305
- # The :double contract: a wholly quoted row split on quote-separator-quote, a wholly unquoted one plainly; nil for a row that is neither.
305
+ # The :double contract: a wholly quoted row split on quote-separator-quote, a wholly unquoted one plainly; nil for a row that is neither, or whose last field does not close.
306
306
  def split_double_quoted(row)
307
307
  if row.start_with?('"')
308
- unescaped_fields(split_on(row.delete_prefix('"').delete_suffix('"'), quoted_separator))
308
+ unescaped_fields(split_on(row.delete_prefix('"').delete_suffix('"'), quoted_separator)) if trailing_quotes(row, 0).odd?
309
309
  else
310
310
  split_on(row, @column_separator) unless row.include?('"')
311
311
  end
@@ -332,7 +332,7 @@ class DSV
332
332
  )
333
333
  end
334
334
 
335
- # Pieces of a row split on the separator, the pieces of a quoted field that held the separator joined back with it, or with the text it matched where the separator is a Regexp; a piece closes a field where it ends in an odd run of quotes, a doubled quote being escaped, and a piece that is a lone quote opens or closes one.
335
+ # Pieces of a row split on the separator, the pieces of a quoted field that held the separator joined back with it, or with the text it matched where the separator is a Regexp; a piece closes a field where it ends in an odd run of quotes, a doubled quote being escaped, and a piece that is a lone quote opens or closes one; nil where a field is still open after the last piece.
336
336
  def reassemble_quoted_fields(pieces, separators = nil)
337
337
  fields = []
338
338
  buffer = nil
@@ -350,7 +350,7 @@ class DSV
350
350
  fields << piece
351
351
  end
352
352
  end
353
- fields
353
+ fields unless buffer
354
354
  end
355
355
 
356
356
  # How many quotes a piece ends with, counting back to but not including the byte given, so that a field's opening quote is not taken for part of its closing. Read by byte, a quote being 34 in any ASCII-compatible encoding, so that nothing is allocated per character.
@@ -365,30 +365,34 @@ class DSV
365
365
  separators ? separators[i - 1] : @column_separator
366
366
  end
367
367
 
368
- # A quoted field may hold the row separator: while the quotes in a row are unbalanced the next line belongs to it, in every quote mode which reads quotes as structure, :none and :unquoted reading them as data.
369
- def complete_quoted_row(raw_row)
370
- raw_row << @source.gets(@row_separator).to_s while @quote != :none && @quote != :unquoted && raw_row.count('"').odd? && !@source.eof?
371
- raw_row
368
+ # A row's fields, the next line taken into the row while a quoted field in it is open, a quoted field being able to hold the row separator; a field still open at the end of the source is dropped, with its row.
369
+ def row_fields(raw_row)
370
+ raw_row << @source.gets(@row_separator) until (fields = split_row(raw_row)) || @source.eof?
371
+ fields || []
372
372
  end
373
373
 
374
374
  def parse_row(raw_row, *selected_columns)
375
+ parse_fields(split_row(raw_row) || [], *selected_columns)
376
+ end
377
+
378
+ def parse_fields(fields, *selected_columns)
375
379
  parsed_row = {}
376
380
  i = -1
377
381
  if selected_columns.empty?
378
382
  if blank?(@columns)
379
- split_row(raw_row).each{|column_value| parsed_row[i += 1] = column_value}
383
+ fields.each{|column_value| parsed_row[i += 1] = column_value}
380
384
  elsif repeated_names.empty?
381
- split_row(raw_row).each{|column_value| parsed_row[attributes[i += 1]] = column_value}
385
+ fields.each{|column_value| parsed_row[attributes[i += 1]] = column_value}
382
386
  else
383
- split_row(raw_row).each{|column_value| store_field(parsed_row, attributes[i += 1], column_value)}
387
+ fields.each{|column_value| store_field(parsed_row, attributes[i += 1], column_value)}
384
388
  end
385
389
  else
386
390
  selected_columns.flatten!
387
391
  case selected_columns[0]
388
392
  when Integer
389
- split_row(raw_row).each{|column_value| parsed_row[i] = column_value unless !selected_columns.include?(i += 1)}
393
+ fields.each{|column_value| parsed_row[i] = column_value unless !selected_columns.include?(i += 1)}
390
394
  else
391
- split_row(raw_row).each{|column_value| store_field(parsed_row, attributes[i], column_value) unless !selected_columns.include?(attributes[i += 1])}
395
+ fields.each{|column_value| store_field(parsed_row, attributes[i], column_value) unless !selected_columns.include?(attributes[i += 1])}
392
396
  end
393
397
  end
394
398
  if @as_array
data/test/dsv_test.rb CHANGED
@@ -562,13 +562,17 @@ describe DSV do
562
562
  end
563
563
 
564
564
  it "reads a stray quote in an unquoted field as data, without taking in the next line" do
565
- skip "Observation: the quotes in a row are counted, so a stray quote leaves them unbalanced and the next line is taken into the row; unspecified since 0.11.4, :double since 0.12.5"
566
565
  _(DSV.read("12\" pizza,9.99\nnext,row\n", as_array: true)).must_equal [['12" pizza', '9.99'], ['next', 'row']]
567
566
  _(DSV.read("12\" pizza,9.99\nnext,row\n", as_array: true, quote: :double)).must_equal [['12" pizza', '9.99'], ['next', 'row']]
568
567
  end
569
568
 
569
+ it "reads a quoted field across lines after a stray quote in the same row" do
570
+ _(DSV.read("a\"b,\"c\nd\"\n", as_array: true)).must_equal [['a"b', "c\nd"]]
571
+ _(DSV.read("a\"b,\"c\nd\"\n", as_array: true, quote: :double)).must_equal [['a"b', "c\nd"]]
572
+ end
573
+
570
574
  it "raises UnterminatedQuote on a quoted field which does not close" do
571
- skip "Observation: a quoted field which does not close is dropped silently, with the rest of its row"
575
+ skip "Observation: a quoted field which does not close is dropped silently, with its row"
572
576
  _{DSV.read("\"abc,d\n")}.must_raise DSV::UnterminatedQuote
573
577
  _{DSV.read("\"ab\"c,d\n")}.must_raise DSV::UnterminatedQuote
574
578
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.7
4
+ version: 0.12.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran