@@ -1508,10 +1508,8 @@ def generate_lines(rows, **options)
1508
1508
1509
1509
#
1510
1510
# :call-seq:
1511
- # open(file_path, mode = "rb", **options ) -> new_csv
1512
- # open(io, mode = "rb", **options ) -> new_csv
1513
- # open(file_path, mode = "rb", **options ) { |csv| ... } -> object
1514
- # open(io, mode = "rb", **options ) { |csv| ... } -> object
1511
+ # open(path_or_io, mode = "rb", **options ) -> new_csv
1512
+ # open(path_or_io, mode = "rb", **options ) { |csv| ... } -> object
1515
1513
#
1516
1514
# possible options elements:
1517
1515
# keyword form:
@@ -1520,7 +1518,7 @@ def generate_lines(rows, **options)
1520
1518
# :undef => :replace # replace undefined conversion
1521
1519
# :replace => string # replacement string ("?" or "\uFFFD" if not specified)
1522
1520
#
1523
- # * Argument +path +, if given, must be the path to a file .
1521
+ # * Argument +path_or_io +, must be a file path or an \IO stream .
1524
1522
# :include: ../doc/csv/arguments/io.rdoc
1525
1523
# * Argument +mode+, if given, must be a \File mode.
1526
1524
# See {Access Modes}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Access+Modes].
@@ -1544,6 +1542,9 @@ def generate_lines(rows, **options)
1544
1542
# path = 't.csv'
1545
1543
# File.write(path, string)
1546
1544
#
1545
+ # string_io = StringIO.new
1546
+ # string_io << "foo,0\nbar,1\nbaz,2\n"
1547
+ #
1547
1548
# ---
1548
1549
#
1549
1550
# With no block given, returns a new \CSV object.
@@ -1556,6 +1557,9 @@ def generate_lines(rows, **options)
1556
1557
# csv = CSV.open(File.open(path))
1557
1558
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1558
1559
#
1560
+ # Create a \CSV object using a \StringIO:
1561
+ # csv = CSV.open(string_io)
1562
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1559
1563
# ---
1560
1564
#
1561
1565
# With a block given, calls the block with the created \CSV object;
@@ -1573,16 +1577,24 @@ def generate_lines(rows, **options)
1573
1577
# Output:
1574
1578
# #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1575
1579
#
1580
+ # Using a \StringIO:
1581
+ # csv = CSV.open(string_io) {|csv| p csv}
1582
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1583
+ # Output:
1584
+ # #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1576
1585
# ---
1577
1586
#
1578
1587
# Raises an exception if the argument is not a \String object or \IO object:
1579
1588
# # Raises TypeError (no implicit conversion of Symbol into String)
1580
1589
# CSV.open(:foo)
1581
- def open ( filename , mode = "r" , **options )
1590
+ def open ( filename_or_io , mode = "r" , **options )
1582
1591
# wrap a File opened with the remaining +args+ with no newline
1583
1592
# decorator
1584
1593
file_opts = { }
1585
- may_enable_bom_deletection_automatically ( mode , options , file_opts )
1594
+ may_enable_bom_detection_automatically ( filename_or_io ,
1595
+ mode ,
1596
+ options ,
1597
+ file_opts )
1586
1598
file_opts . merge! ( options )
1587
1599
unless file_opts . key? ( :newline )
1588
1600
file_opts [ :universal_newline ] ||= false
@@ -1592,14 +1604,19 @@ def open(filename, mode="r", **options)
1592
1604
options . delete ( :replace )
1593
1605
options . delete_if { |k , _ | /newline\z / . match? ( k ) }
1594
1606
1595
- begin
1596
- f = File . open ( filename , mode , **file_opts )
1597
- rescue ArgumentError => e
1598
- raise unless /needs binmode/ . match? ( e . message ) and mode == "r"
1599
- mode = "rb"
1600
- file_opts = { encoding : Encoding . default_external } . merge ( file_opts )
1601
- retry
1607
+ if filename_or_io . is_a? ( StringIO )
1608
+ f = create_stringio ( filename_or_io . string , mode , **file_opts )
1609
+ else
1610
+ begin
1611
+ f = File . open ( filename_or_io , mode , **file_opts )
1612
+ rescue ArgumentError => e
1613
+ raise unless /needs binmode/ . match? ( e . message ) and mode == "r"
1614
+ mode = "rb"
1615
+ file_opts = { encoding : Encoding . default_external } . merge ( file_opts )
1616
+ retry
1617
+ end
1602
1618
end
1619
+
1603
1620
begin
1604
1621
csv = new ( f , **options )
1605
1622
rescue Exception
@@ -1886,16 +1903,37 @@ def table(path, **options)
1886
1903
private_constant :ON_WINDOWS
1887
1904
1888
1905
private
1889
- def may_enable_bom_deletection_automatically ( mode , options , file_opts )
1890
- # "bom|utf-8" may be buggy on Windows:
1891
- # https://bugs.ruby-lang.org/issues/20526
1892
- return if ON_WINDOWS
1906
+ def may_enable_bom_detection_automatically ( filename_or_io ,
1907
+ mode ,
1908
+ options ,
1909
+ file_opts )
1910
+ if filename_or_io . is_a? ( StringIO )
1911
+ # Support to StringIO was dropped for Ruby 2.6 and earlier without BOM support:
1912
+ # https://github.com/ruby/stringio/pull/47
1913
+ return if RUBY_VERSION < "2.7"
1914
+ else
1915
+ # "bom|utf-8" may be buggy on Windows:
1916
+ # https://bugs.ruby-lang.org/issues/20526
1917
+ return if ON_WINDOWS
1918
+ end
1893
1919
return unless Encoding . default_external == Encoding ::UTF_8
1894
1920
return if options . key? ( :encoding )
1895
1921
return if options . key? ( :external_encoding )
1896
1922
return if mode . include? ( ":" )
1897
1923
file_opts [ :encoding ] = "bom|utf-8"
1898
1924
end
1925
+
1926
+ if RUBY_VERSION < "2.7"
1927
+ def create_stringio ( str , mode , opts )
1928
+ opts . delete_if { |k , _ | k == :universal_newline or DEFAULT_OPTIONS . key? ( k ) }
1929
+ raise ArgumentError , "Unsupported options parsing StringIO: #{ opts . keys } " unless opts . empty?
1930
+ StringIO . new ( str , mode )
1931
+ end
1932
+ else
1933
+ def create_stringio ( str , mode , opts )
1934
+ StringIO . new ( str , mode , **opts )
1935
+ end
1936
+ end
1899
1937
end
1900
1938
1901
1939
# :call-seq:
0 commit comments