dsv 0.12.8 → 0.12.9

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: 190e66c0b25b64c18dac0b955ae59fe64591d14d369cafa950d31b1aa56a2b30
4
- data.tar.gz: 6a7ea034b8f8da40716efb89a555951307ee312c38142b84c71f2712c57e0622
3
+ metadata.gz: 7171a2d2de4dba26f3e304ca3e0c216e9bb093a36cef1420629e9f936e644889
4
+ data.tar.gz: 4ccf92f6000c7e5b9d82315babc17c816edd3df7587583eb79f2c16eb19125cb
5
5
  SHA512:
6
- metadata.gz: e6f6cb3c8f04131f26bf26be33f7dc1e000a4e39a58537f3c327c250837d880477b07e478be8688a57b124dfd85f24f92d569109de15e95ec61b95631652e493
7
- data.tar.gz: f0787b59e38dbac938fcd7474b815e24d2660c33438289a9720ca239b439a3d7435ce650db9dd5279b2ae2266ccfe763d1dfe8005d92cdb2dd1a8c1369f16cb8
6
+ metadata.gz: 4727efe3e9571299914d4b6731169ac5a70eabd475176f06cb084dba35cfcc038420faf4740ad01bbedb9caea1b13ba21ed40a058f2699ca6f4fedf9ba47a0b1
7
+ data.tar.gz: 6b6143f85e9c6106344fed384228433057eff0641a38eff9e6f823a2d905cca416284b7068f80ff26529feb02045fe09f323e689614012bd267d0783aec469ba
data/CHANGELOG CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## 20261028
4
4
 
5
+ 0.12.9: + DSV::UnterminatedQuote, raised where a quoted field does not close.
6
+
7
+ 1. + lib/DSV/UnterminatedQuote.rb: DSV::UnterminatedQuote, with line, the line on which the field's row began.
8
+ 2. ~ row_fields(), parse_row(), columns(): raise it where a quoted field is still open at the end of the source, or of the line or header given. Such a field had been dropped silently, with its row.
9
+ 3. + line_of(): counts the lines before the row only when raising.
10
+ 4. ~ test/dsv_test.rb: - the skip on a quoted field which does not close; + the line it names; the malformed examples now state the default rule.
11
+ 5. ~ README.md: Quoting, the rule for input which isn't RFC 4180.
12
+ 6. ~ TODO: 12, as UnterminatedQuote finds its line; + 15, the rule as an option.
13
+ 7. ~ lib/DSV/VERSION.rb: /0.12.8/0.12.9/.
14
+
5
15
  0.12.8: a stray quote no longer takes in the next line.
6
16
 
7
17
  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.
data/README.md CHANGED
@@ -89,6 +89,8 @@ DSV.read('quoted.csv', headers: true, quote: :double) # every field is quoted,
89
89
 
90
90
  Under `:double` a row that is wholly unquoted, as a header row often is, is split plainly, and a row that is neither wholly quoted nor wholly unquoted is read as it would be with no mode named, so naming the mode never costs a correct read.
91
91
 
92
+ Input which isn't RFC 4180 is read to one rule. A quote opens a field only at the start of one, so a quote anywhere else, as in `12" pizza`, is data. A quoted field closes only at a quote followed by the separator or the end of the row, so one which doesn't close by the end of the source, as `"abc` or `"ab"c` doesn't, raises `DSV::UnterminatedQuote`, whose `line` is the line its row began on.
93
+
92
94
  ### Writing
93
95
 
94
96
  Set the columns, give the rows as Hashes, and write:
data/TODO CHANGED
@@ -19,6 +19,7 @@
19
19
  9. Writing options: minimal quoting, a field quoted only where it holds a separator, a quote or a row terminator; a row terminator inside a field kept, quoted, or folded to a space; the encoding declared and a transcode offered with any loss marked.
20
20
  10. Symbols and Strings interchangeable for column names, in a selection, a columns: list and a row's keys; today a String is required.
21
21
  11. A strict: option, off by default, that raises on a row with more or fewer fields than the header; today a short row gets only the columns it has and a long row's excess lands under nil.
22
- 12. A line number in each parse error, UnterminatedQuote first, found only when the error is raised, from the source's position at that moment; never counted as it goes, since counting costs time on every row for the sake of the rare one.
22
+ 12. A line number in each parse error, found only when the error is raised, from the source's position at that moment, as UnterminatedQuote finds its; never counted as it goes, since counting costs time on every row for the sake of the rare one.
23
23
  13. Chunked processing of a large file: a bounded number of rows at a time handed to a block, so that nothing needs the whole file in memory and a partial result can be acted on before the rest is read.
24
24
  14. Checks, all of them after the above: strict: (13), column types, and whatever else proves wanted; correct and published comes first.
25
+ 15. The rule for malformed quoting as an option, its name to be settled, perhaps one with strict: (11): the default, as now; a stricter one, under which a quote that does not open a field raises too, and :double raises on a row which is not what it promises rather than reading it as unspecified quoting does; and a lenient one, never raising, text after a closing quote kept and a field which does not close running to the end of the source.
@@ -0,0 +1,14 @@
1
+ # DSV/UnterminatedQuote.rb
2
+ # DSV::UnterminatedQuote
3
+
4
+ class DSV
5
+ # A quoted field which does not close by the end of the source; line is the one on which its row began.
6
+ class UnterminatedQuote < ::StandardError
7
+ attr_reader :line
8
+
9
+ def initialize(line)
10
+ @line = line
11
+ super("A quoted field in the row beginning on line #{line} does not close.")
12
+ end
13
+ end
14
+ end
data/lib/DSV/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # DSV::VERSION
3
3
 
4
4
  class DSV
5
- VERSION = '0.12.8'
5
+ VERSION = '0.12.9'
6
6
  end
data/lib/dsv.rb CHANGED
@@ -8,6 +8,7 @@ require 'stringio'
8
8
  require 'DSV/VERSION'
9
9
  require 'DSV/File'
10
10
  require 'DSV/String'
11
+ require 'DSV/UnterminatedQuote'
11
12
 
12
13
  class DSV
13
14
 
@@ -211,7 +212,7 @@ class DSV
211
212
  @columns = (
212
213
  if header_row? && ['r', 'r+', 'a+'].include?(@mode) && (first_row = first_row?)
213
214
  columns, i = {}, -1
214
- (split_row(first_row) || []).each do |column_name|
215
+ (split_row(first_row) || raise(UnterminatedQuote.new(1))).each do |column_name|
215
216
  columns[column_name] = columns.key?(column_name) ? [*columns[column_name], i += 1] : (i += 1)
216
217
  end
217
218
  columns
@@ -365,14 +366,21 @@ class DSV
365
366
  separators ? separators[i - 1] : @column_separator
366
367
  end
367
368
 
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
+ # 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 raises UnterminatedQuote.
369
370
  def row_fields(raw_row)
370
371
  raw_row << @source.gets(@row_separator) until (fields = split_row(raw_row)) || @source.eof?
371
- fields || []
372
+ fields || raise(UnterminatedQuote.new(line_of(raw_row)))
373
+ end
374
+
375
+ # The line on which a row read up to the source's position began, counted only when an error wants it, by reading the source again up to the row, so that no row which closes pays for it.
376
+ def line_of(raw_row)
377
+ start = @source.pos - raw_row.bytesize
378
+ @source.rewind
379
+ @source.read(start).to_s.scan(@row_separator).length + 1
372
380
  end
373
381
 
374
382
  def parse_row(raw_row, *selected_columns)
375
- parse_fields(split_row(raw_row) || [], *selected_columns)
383
+ parse_fields(split_row(raw_row) || raise(UnterminatedQuote.new(1)), *selected_columns)
376
384
  end
377
385
 
378
386
  def parse_fields(fields, *selected_columns)
data/test/dsv_test.rb CHANGED
@@ -554,7 +554,7 @@ describe DSV do
554
554
  end
555
555
  end
556
556
 
557
- # Input which is not RFC 4180, read to one rule provisionally, until a policy for malformed quoting is decided: a quote is structure only where it opens a field, and a field it opens which does not close raises UnterminatedQuote.
557
+ # Input which is not RFC 4180, read to the default rule: a quote is structure only where it opens a field, and a field it opens which does not close raises UnterminatedQuote.
558
558
  describe "quoting on read, where the input is malformed" do
559
559
  it "reads a quote which does not open a field as data" do
560
560
  _(DSV.parse_line('a"b"c,d')).must_equal ['a"b"c', 'd']
@@ -572,9 +572,18 @@ describe DSV do
572
572
  end
573
573
 
574
574
  it "raises UnterminatedQuote on a quoted field which does not close" do
575
- skip "Observation: a quoted field which does not close is dropped silently, with its row"
576
575
  _{DSV.read("\"abc,d\n")}.must_raise DSV::UnterminatedQuote
577
576
  _{DSV.read("\"ab\"c,d\n")}.must_raise DSV::UnterminatedQuote
577
+ _{DSV.read("x,\"abc\n", quote: :double)}.must_raise DSV::UnterminatedQuote
578
+ _{DSV.parse_line('x,"abc')}.must_raise DSV::UnterminatedQuote
579
+ end
580
+
581
+ it "names the line on which the row holding the unterminated field began" do
582
+ error = _{DSV.read("a,b\n\"c\nd\",e\nf,\"g\nh\n")}.must_raise DSV::UnterminatedQuote
583
+ _(error.line).must_equal 4
584
+ _(error.message).must_include 'line 4'
585
+ error = _{DSV.read("name,note\nAda,\"unclosed\n", headers: true)}.must_raise DSV::UnterminatedQuote
586
+ _(error.line).must_equal 2
578
587
  end
579
588
  end
580
589
 
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.8
4
+ version: 0.12.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -67,6 +67,7 @@ files:
67
67
  - dsv.gemspec
68
68
  - lib/DSV/File.rb
69
69
  - lib/DSV/String.rb
70
+ - lib/DSV/UnterminatedQuote.rb
70
71
  - lib/DSV/VERSION.rb
71
72
  - lib/dsv.rb
72
73
  - test/VERSION_test.rb