csv 3.0.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25367f06751ab916228ddcffcbc857bc13ca1e7fcc65a908fcfc7c974e5473f6
4
- data.tar.gz: 92ff4c8f3b96219b9d74fc849311afd0cb97f3d124c77250878402fc006ce2ac
3
+ metadata.gz: a240b6e6a405972ac623bbc1c369d6833e57b449606ce02d01b183604d621353
4
+ data.tar.gz: 73cb12bafe60a7331b13d9d7b75837007c55044b78d4580d422ba123af692d25
5
5
  SHA512:
6
- metadata.gz: 25584f3c7ccf6ffa990dfa5a58cb4564092f51c3145fccb0b54252210096d8d322044c72647be36d7151362fe02e76c3ab103f99615ac42e9f1621ba6f2e9aa4
7
- data.tar.gz: 2e0bb6973a005ae822b08bf6c13ff1681bf7c599e2c966f8bfd19ecffc6083fb09a03b74bbb47019a44bda729c0d173bed864815bffa670d19684092da0f128d
6
+ metadata.gz: 739201c445c8b6ad1644d2ede0522635d10b83a1a2253cea7386190749b762d0301fc15af98c55976180263a210b5c954f49428897760682675db420704543a6
7
+ data.tar.gz: 6b55c8703407e7fdd09075ce5ce798d66fad1317b65b19b035cde5653f29c13087775cbf1117cdb044689b6a33c031c9624c503eb3c441a817741fdc5dc6084e
data/NEWS.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # News
2
2
 
3
+ ## 3.0.3 - 2019-01-12
4
+
5
+ ### Improvements
6
+
7
+ * Migrated benchmark tool to benchmark-driver from benchmark-ips.
8
+ [GitHub#57][Patch by 284km]
9
+
10
+ * Added `liberal_parsing: {double_quote_outside_quote: true}` parse
11
+ option.
12
+ [GitHub#66][Reported by Watson]
13
+
14
+ * Added `quote_empty:` write option.
15
+ [GitHub#35][Reported by Dave Myron]
16
+
17
+ ### Fixes
18
+
19
+ * Fixed a compatibility bug that `CSV.generate` always return
20
+ `ASCII-8BIT` encoding string.
21
+ [GitHub#63][Patch by Watson]
22
+
23
+ * Fixed a compatibility bug that `CSV.parse("", headers: true)`
24
+ doesn't return `CSV::Table`.
25
+ [GitHub#64][Reported by Watson][Patch by 284km]
26
+
27
+ * Fixed a compatibility bug that multiple-characters column
28
+ separator doesn't work.
29
+ [GitHub#67][Reported by Jesse Reiss]
30
+
31
+ * Fixed a compatibility bug that double `#each` parse twice.
32
+ [GitHub#68][Reported by Max Schwenk]
33
+
34
+ ### Thanks
35
+
36
+ * Watson
37
+
38
+ * 284km
39
+
40
+ * Jesse Reiss
41
+
42
+ * Dave Myron
43
+
44
+ * Max Schwenk
45
+
3
46
  ## 3.0.2 - 2018-12-23
4
47
 
5
48
  ### Improvements
data/lib/csv.rb CHANGED
@@ -397,6 +397,7 @@ class CSV
397
397
  # <b><tt>:force_quotes</tt></b>:: +false+
398
398
  # <b><tt>:skip_lines</tt></b>:: +nil+
399
399
  # <b><tt>:liberal_parsing</tt></b>:: +false+
400
+ # <b><tt>:quote_empty</tt></b>:: +true+
400
401
  #
401
402
  DEFAULT_OPTIONS = {
402
403
  col_sep: ",",
@@ -412,6 +413,7 @@ class CSV
412
413
  force_quotes: false,
413
414
  skip_lines: nil,
414
415
  liberal_parsing: false,
416
+ quote_empty: true,
415
417
  }.freeze
416
418
 
417
419
  #
@@ -534,7 +536,7 @@ class CSV
534
536
  str.seek(0, IO::SEEK_END)
535
537
  else
536
538
  encoding = options[:encoding]
537
- str = String.new
539
+ str = +""
538
540
  str.force_encoding(encoding) if encoding
539
541
  end
540
542
  csv = new(str, options) # wrap
@@ -557,11 +559,11 @@ class CSV
557
559
  #
558
560
  def self.generate_line(row, **options)
559
561
  options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
560
- str = String.new
562
+ str = +""
561
563
  if options[:encoding]
562
564
  str.force_encoding(options[:encoding])
563
- elsif field = row.find { |f| not f.nil? }
564
- str.force_encoding(String(field).encoding)
565
+ elsif field = row.find {|f| f.is_a?(String)}
566
+ str.force_encoding(field.encoding)
565
567
  end
566
568
  (new(str, options) << row).string
567
569
  end
@@ -882,6 +884,7 @@ class CSV
882
884
  # <b><tt>:empty_value</tt></b>:: When set an object, any values of a
883
885
  # blank string field is replaced by
884
886
  # the set object.
887
+ # <b><tt>:quote_empty</tt></b>:: TODO
885
888
  #
886
889
  # See CSV::DEFAULT_OPTIONS for the default settings.
887
890
  #
@@ -907,7 +910,8 @@ class CSV
907
910
  external_encoding: nil,
908
911
  encoding: nil,
909
912
  nil_value: nil,
910
- empty_value: "")
913
+ empty_value: "",
914
+ quote_empty: true)
911
915
  raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
912
916
 
913
917
  # create the IO object we will read from
@@ -947,6 +951,7 @@ class CSV
947
951
  column_separator: col_sep,
948
952
  row_separator: row_sep,
949
953
  quote_character: quote_char,
954
+ quote_empty: quote_empty,
950
955
  }
951
956
 
952
957
  @writer = nil
@@ -1178,9 +1183,8 @@ class CSV
1178
1183
  #
1179
1184
  def read
1180
1185
  rows = to_a
1181
- headers = parser.headers
1182
- if headers
1183
- Table.new(rows, headers: headers)
1186
+ if parser.use_headers?
1187
+ Table.new(rows, headers: parser.headers)
1184
1188
  else
1185
1189
  rows
1186
1190
  end
@@ -170,6 +170,7 @@ class CSV
170
170
  @input = input
171
171
  @options = options
172
172
  @samples = []
173
+ @parsed = false
173
174
 
174
175
  prepare
175
176
  end
@@ -229,6 +230,8 @@ class CSV
229
230
  def parse(&block)
230
231
  return to_enum(__method__) unless block_given?
231
232
 
233
+ return if @parsed
234
+
232
235
  if @return_headers and @headers
233
236
  headers = Row.new(@headers, @raw_headers, true)
234
237
  if @unconverted_fields
@@ -262,10 +265,10 @@ class CSV
262
265
  skip_needless_lines
263
266
  start_row
264
267
  elsif @scanner.eos?
265
- return if row.empty? and value.nil?
268
+ break if row.empty? and value.nil?
266
269
  row << value
267
270
  emit_row(row, &block)
268
- return
271
+ break
269
272
  else
270
273
  if @quoted_column_value
271
274
  message = "Do not allow except col_sep_split_separator " +
@@ -287,6 +290,12 @@ class CSV
287
290
  message = "Invalid byte sequence in #{@encoding}"
288
291
  raise MalformedCSVError.new(message, @lineno + 1)
289
292
  end
293
+
294
+ @parsed = true
295
+ end
296
+
297
+ def use_headers?
298
+ @use_headers
290
299
  end
291
300
 
292
301
  private
@@ -300,7 +309,18 @@ class CSV
300
309
 
301
310
  def prepare_variable
302
311
  @encoding = @options[:encoding]
303
- @liberal_parsing = @options[:liberal_parsing]
312
+ liberal_parsing = @options[:liberal_parsing]
313
+ if liberal_parsing
314
+ @liberal_parsing = true
315
+ if liberal_parsing.is_a?(Hash)
316
+ @double_quote_outside_quote =
317
+ liberal_parsing[:double_quote_outside_quote]
318
+ else
319
+ @double_quote_outside_quote = false
320
+ end
321
+ else
322
+ @liberal_parsing = false
323
+ end
304
324
  @unconverted_fields = @options[:unconverted_fields]
305
325
  @field_size_limit = @options[:field_size_limit]
306
326
  @skip_blanks = @options[:skip_blanks]
@@ -318,6 +338,7 @@ class CSV
318
338
  end
319
339
 
320
340
  escaped_column_separator = Regexp.escape(@column_separator)
341
+ escaped_first_column_separator = Regexp.escape(@column_separator[0])
321
342
  escaped_row_separator = Regexp.escape(@row_separator)
322
343
  escaped_quote_character = Regexp.escape(@quote_character)
323
344
 
@@ -341,8 +362,11 @@ class CSV
341
362
  @column_ends = @column_separator.each_char.collect do |char|
342
363
  Regexp.new(Regexp.escape(char))
343
364
  end
365
+ @first_column_separators = Regexp.new(escaped_first_column_separator +
366
+ "+".encode(@encoding))
344
367
  else
345
368
  @column_ends = nil
369
+ @first_column_separators = nil
346
370
  end
347
371
  @row_end = Regexp.new(escaped_row_separator)
348
372
  if @row_separator.size > 1
@@ -359,12 +383,12 @@ class CSV
359
383
  "]+".encode(@encoding))
360
384
  if @liberal_parsing
361
385
  @unquoted_value = Regexp.new("[^".encode(@encoding) +
362
- escaped_column_separator +
386
+ escaped_first_column_separator +
363
387
  "\r\n]+".encode(@encoding))
364
388
  else
365
389
  @unquoted_value = Regexp.new("[^".encode(@encoding) +
366
390
  escaped_quote_character +
367
- escaped_column_separator +
391
+ escaped_first_column_separator +
368
392
  "\r\n]+".encode(@encoding))
369
393
  end
370
394
  @cr_or_lf = Regexp.new("[\r\n]".encode(@encoding))
@@ -583,6 +607,13 @@ class CSV
583
607
  if quoted_value
584
608
  unquoted_value = parse_unquoted_column_value
585
609
  if unquoted_value
610
+ if @double_quote_outside_quote
611
+ unquoted_value = unquoted_value.gsub(@quote_character * 2,
612
+ @quote_character)
613
+ if quoted_value.empty? # %Q{""...} case
614
+ return @quote_character + unquoted_value
615
+ end
616
+ end
586
617
  @quote_character + quoted_value + @quote_character + unquoted_value
587
618
  else
588
619
  quoted_value
@@ -601,7 +632,25 @@ class CSV
601
632
 
602
633
  def parse_unquoted_column_value
603
634
  value = @scanner.scan_all(@unquoted_value)
604
- @unquoted_column_value = true if value
635
+ return nil unless value
636
+
637
+ @unquoted_column_value = true
638
+ if @first_column_separators
639
+ while true
640
+ @scanner.keep_start
641
+ is_column_end = @column_ends.all? do |column_end|
642
+ @scanner.scan(column_end)
643
+ end
644
+ @scanner.keep_back
645
+ break if is_column_end
646
+ sub_separator = @scanner.scan_all(@first_column_separators)
647
+ break if sub_separator.nil?
648
+ value << sub_separator
649
+ sub_value = @scanner.scan_all(@unquoted_value)
650
+ break if sub_value.nil?
651
+ value << sub_value
652
+ end
653
+ end
605
654
  value
606
655
  end
607
656
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  class CSV
4
4
  # The version of the installed library.
5
- VERSION = "3.0.2"
5
+ VERSION = "3.0.3"
6
6
  end
@@ -31,7 +31,10 @@ class CSV
31
31
  @headers ||= row if @use_headers
32
32
  @lineno += 1
33
33
 
34
- line = row.collect(&@quote).join(@column_separator) + @row_separator
34
+ converted_row = row.collect do |field|
35
+ quote(field)
36
+ end
37
+ line = converted_row.join(@column_separator) + @row_separator
35
38
  if @output_encoding
36
39
  line = line.encode(@output_encoding)
37
40
  end
@@ -90,37 +93,16 @@ class CSV
90
93
  else
91
94
  @row_separator = row_separator.to_s.encode(@encoding)
92
95
  end
93
- quote_character = @options[:quote_character]
94
- quote = lambda do |field|
95
- field = String(field)
96
- encoded_quote_character = quote_character.encode(field.encoding)
97
- encoded_quote_character +
98
- field.gsub(encoded_quote_character,
99
- encoded_quote_character * 2) +
100
- encoded_quote_character
101
- end
102
- if @options[:force_quotes]
103
- @quote = quote
104
- else
105
- quotable_pattern =
96
+ @quote_character = @options[:quote_character]
97
+ @force_quotes = @options[:force_quotes]
98
+ unless @force_quotes
99
+ @quotable_pattern =
106
100
  Regexp.new("[\r\n".encode(@encoding) +
107
101
  Regexp.escape(@column_separator) +
108
- Regexp.escape(quote_character.encode(@encoding)) +
102
+ Regexp.escape(@quote_character.encode(@encoding)) +
109
103
  "]".encode(@encoding))
110
- @quote = lambda do |field|
111
- if field.nil? # represent +nil+ fields as empty unquoted fields
112
- ""
113
- else
114
- field = String(field) # Stringify fields
115
- # represent empty fields as empty quoted fields
116
- if field.empty? or quotable_pattern.match?(field)
117
- quote.call(field)
118
- else
119
- field # unquoted field
120
- end
121
- end
122
- end
123
104
  end
105
+ @quote_empty = @options.fetch(:quote_empty, true)
124
106
  end
125
107
 
126
108
  def prepare_output
@@ -140,5 +122,32 @@ class CSV
140
122
  end
141
123
  end
142
124
  end
125
+
126
+ def quote_field(field)
127
+ field = String(field)
128
+ encoded_quote_character = @quote_character.encode(field.encoding)
129
+ encoded_quote_character +
130
+ field.gsub(encoded_quote_character,
131
+ encoded_quote_character * 2) +
132
+ encoded_quote_character
133
+ end
134
+
135
+ def quote(field)
136
+ if @force_quotes
137
+ quote_field(field)
138
+ else
139
+ if field.nil? # represent +nil+ fields as empty unquoted fields
140
+ ""
141
+ else
142
+ field = String(field) # Stringify fields
143
+ # represent empty fields as empty quoted fields
144
+ if (@quote_empty and field.empty?) or @quotable_pattern.match?(field)
145
+ quote_field(field)
146
+ else
147
+ field # unquoted field
148
+ end
149
+ end
150
+ end
151
+ end
143
152
  end
144
153
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Edward Gray II
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-12-23 00:00:00.000000000 Z
12
+ date: 2019-01-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -40,7 +40,7 @@ dependencies:
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: benchmark-ips
43
+ name: benchmark_driver
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="
@@ -109,8 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 3.0.0.beta2
112
+ rubygems_version: 3.0.2
114
113
  signing_key:
115
114
  specification_version: 4
116
115
  summary: CSV Reading and Writing