brakeman 6.2.2 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +17 -0
  3. data/bundle/load.rb +7 -6
  4. data/bundle/ruby/3.1.0/gems/csv-3.3.2/LICENSE.txt +33 -0
  5. data/bundle/ruby/3.1.0/gems/csv-3.3.2/NEWS.md +965 -0
  6. data/bundle/ruby/3.1.0/gems/csv-3.3.2/README.md +55 -0
  7. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/core_ext/array.rb +9 -0
  8. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/core_ext/string.rb +9 -0
  9. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/fields_converter.rb +96 -0
  10. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/input_record_separator.rb +18 -0
  11. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/parser.rb +1292 -0
  12. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/row.rb +757 -0
  13. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/table.rb +1055 -0
  14. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/version.rb +6 -0
  15. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv/writer.rb +209 -0
  16. data/bundle/ruby/3.1.0/gems/csv-3.3.2/lib/csv.rb +3017 -0
  17. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/config.rb +22 -26
  18. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/history.rb +3 -3
  19. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/io/ansi.rb +64 -111
  20. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/io/dumb.rb +16 -2
  21. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/io/windows.rb +77 -60
  22. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/io.rb +14 -0
  23. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor/base.rb +10 -4
  24. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor/emacs.rb +96 -96
  25. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor/vi_command.rb +182 -182
  26. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor/vi_insert.rb +137 -137
  27. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_stroke.rb +26 -16
  28. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/line_editor.rb +238 -404
  29. data/bundle/ruby/3.1.0/gems/reline-0.6.0/lib/reline/unicode.rb +415 -0
  30. data/bundle/ruby/3.1.0/gems/reline-0.6.0/lib/reline/version.rb +3 -0
  31. data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline.rb +18 -18
  32. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/NEWS.md +50 -0
  33. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/baseparser.rb +54 -45
  34. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/pullparser.rb +4 -0
  35. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/rexml.rb +1 -1
  36. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/source.rb +45 -4
  37. data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/text.rb +15 -40
  38. data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/History.rdoc +6 -0
  39. data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/sexp.rb +1 -1
  40. data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/sexp_processor.rb +1 -1
  41. data/bundle/ruby/3.1.0/gems/terminal-table-3.0.2/History.rdoc +142 -0
  42. data/bundle/ruby/3.1.0/gems/terminal-table-3.0.2/README.md +417 -0
  43. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/cell.rb +8 -8
  44. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/row.rb +18 -4
  45. data/bundle/ruby/3.1.0/gems/terminal-table-3.0.2/lib/terminal-table/separator.rb +66 -0
  46. data/bundle/ruby/3.1.0/gems/terminal-table-3.0.2/lib/terminal-table/style.rb +284 -0
  47. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/table.rb +47 -18
  48. data/bundle/ruby/3.1.0/gems/terminal-table-3.0.2/lib/terminal-table/util.rb +13 -0
  49. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/version.rb +1 -1
  50. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table.rb +2 -2
  51. data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/terminal-table.gemspec +3 -3
  52. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/creole.rb +2 -0
  53. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/liquid.rb +0 -3
  54. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/mapping.rb +3 -3
  55. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/rdoc.rb +0 -8
  56. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/template.rb +27 -3
  57. data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt.rb +1 -4
  58. data/bundle/ruby/3.1.0/gems/{unicode-display_width-1.8.0 → unicode-display_width-2.6.0}/CHANGELOG.md +65 -2
  59. data/bundle/ruby/3.1.0/gems/{unicode-display_width-1.8.0 → unicode-display_width-2.6.0}/MIT-LICENSE.txt +1 -1
  60. data/bundle/ruby/3.1.0/gems/{unicode-display_width-1.8.0 → unicode-display_width-2.6.0}/README.md +67 -20
  61. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/data/display_width.marshal.gz +0 -0
  62. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/lib/unicode/display_width/constants.rb +10 -0
  63. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/lib/unicode/display_width/index.rb +34 -0
  64. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/lib/unicode/display_width/no_string_ext.rb +8 -0
  65. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/lib/unicode/display_width/string_ext.rb +9 -0
  66. data/bundle/ruby/3.1.0/gems/unicode-display_width-2.6.0/lib/unicode/display_width.rb +123 -0
  67. data/lib/brakeman/app_tree.rb +23 -18
  68. data/lib/brakeman/checks/check_deserialize.rb +4 -1
  69. data/lib/brakeman/checks/check_evaluation.rb +20 -2
  70. data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -0
  71. data/lib/brakeman/file_parser.rb +2 -1
  72. data/lib/brakeman/options.rb +8 -5
  73. data/lib/brakeman/processors/alias_processor.rb +6 -2
  74. data/lib/brakeman/processors/lib/file_type_detector.rb +9 -7
  75. data/lib/brakeman/report/ignore/config.rb +0 -1
  76. data/lib/brakeman/report/report_sarif.rb +122 -2
  77. data/lib/brakeman/rescanner.rb +40 -390
  78. data/lib/brakeman/scanner.rb +62 -38
  79. data/lib/brakeman/tracker/file_cache.rb +83 -0
  80. data/lib/brakeman/tracker.rb +19 -2
  81. data/lib/brakeman/version.rb +1 -1
  82. data/lib/brakeman.rb +12 -2
  83. metadata +170 -160
  84. data/bundle/ruby/3.1.0/gems/reline-0.5.10/lib/reline/terminfo.rb +0 -158
  85. data/bundle/ruby/3.1.0/gems/reline-0.5.10/lib/reline/unicode.rb +0 -671
  86. data/bundle/ruby/3.1.0/gems/reline-0.5.10/lib/reline/version.rb +0 -3
  87. data/bundle/ruby/3.1.0/gems/terminal-table-1.8.0/History.rdoc +0 -85
  88. data/bundle/ruby/3.1.0/gems/terminal-table-1.8.0/README.rdoc +0 -247
  89. data/bundle/ruby/3.1.0/gems/terminal-table-1.8.0/lib/terminal-table/separator.rb +0 -14
  90. data/bundle/ruby/3.1.0/gems/terminal-table-1.8.0/lib/terminal-table/style.rb +0 -79
  91. data/bundle/ruby/3.1.0/gems/tilt-2.4.0/lib/tilt/erubis.rb +0 -51
  92. data/bundle/ruby/3.1.0/gems/tilt-2.4.0/lib/tilt/maruku.rb +0 -10
  93. data/bundle/ruby/3.1.0/gems/tilt-2.4.0/lib/tilt/wikicloth.rb +0 -12
  94. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/data/display_width.marshal.gz +0 -0
  95. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/lib/unicode/display_width/constants.rb +0 -8
  96. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/lib/unicode/display_width/index.rb +0 -12
  97. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/lib/unicode/display_width/no_string_ext.rb +0 -7
  98. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/lib/unicode/display_width/string_ext.rb +0 -17
  99. data/bundle/ruby/3.1.0/gems/unicode-display_width-1.8.0/lib/unicode/display_width.rb +0 -51
  100. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/BSDL +0 -0
  101. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/COPYING +0 -0
  102. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/README.md +0 -0
  103. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/face.rb +0 -0
  104. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor/composite.rb +0 -0
  105. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/key_actor.rb +0 -0
  106. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/kill_ring.rb +0 -0
  107. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/lib/reline/unicode/east_asian_width.rb +0 -0
  108. /data/bundle/ruby/3.1.0/gems/{reline-0.5.10 → reline-0.6.0}/license_of_rb-readline +0 -0
  109. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/LICENSE.txt +0 -0
  110. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/README.md +0 -0
  111. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/attlistdecl.rb +0 -0
  112. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/attribute.rb +0 -0
  113. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/cdata.rb +0 -0
  114. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/child.rb +0 -0
  115. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/comment.rb +0 -0
  116. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/doctype.rb +0 -0
  117. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/document.rb +0 -0
  118. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/dtd/attlistdecl.rb +0 -0
  119. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/dtd/dtd.rb +0 -0
  120. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/dtd/elementdecl.rb +0 -0
  121. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/dtd/entitydecl.rb +0 -0
  122. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/dtd/notationdecl.rb +0 -0
  123. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/element.rb +0 -0
  124. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/encoding.rb +0 -0
  125. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/entity.rb +0 -0
  126. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/formatters/default.rb +0 -0
  127. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/formatters/pretty.rb +0 -0
  128. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/formatters/transitive.rb +0 -0
  129. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/functions.rb +0 -0
  130. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/instruction.rb +0 -0
  131. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/light/node.rb +0 -0
  132. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/namespace.rb +0 -0
  133. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/node.rb +0 -0
  134. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/output.rb +0 -0
  135. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parent.rb +0 -0
  136. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parseexception.rb +0 -0
  137. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/lightparser.rb +0 -0
  138. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/sax2parser.rb +0 -0
  139. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/streamparser.rb +0 -0
  140. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/treeparser.rb +0 -0
  141. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/ultralightparser.rb +0 -0
  142. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/parsers/xpathparser.rb +0 -0
  143. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/quickpath.rb +0 -0
  144. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/sax2listener.rb +0 -0
  145. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/security.rb +0 -0
  146. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/streamlistener.rb +0 -0
  147. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/undefinednamespaceexception.rb +0 -0
  148. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/validation/relaxng.rb +0 -0
  149. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/validation/validation.rb +0 -0
  150. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/validation/validationexception.rb +0 -0
  151. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/xmldecl.rb +0 -0
  152. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/xmltokens.rb +0 -0
  153. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/xpath.rb +0 -0
  154. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml/xpath_parser.rb +0 -0
  155. /data/bundle/ruby/3.1.0/gems/{rexml-3.3.8 → rexml-3.4.0}/lib/rexml.rb +0 -0
  156. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/Manifest.txt +0 -0
  157. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/README.rdoc +0 -0
  158. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/composite_sexp_processor.rb +0 -0
  159. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/pt_testcase.rb +0 -0
  160. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/sexp_matcher.rb +0 -0
  161. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/strict_sexp.rb +0 -0
  162. /data/bundle/ruby/3.1.0/gems/{sexp_processor-4.17.2 → sexp_processor-4.17.3}/lib/unique.rb +0 -0
  163. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/Gemfile +0 -0
  164. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/LICENSE.txt +0 -0
  165. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/Manifest +0 -0
  166. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/Todo.rdoc +0 -0
  167. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/import.rb +0 -0
  168. /data/bundle/ruby/3.1.0/gems/{terminal-table-1.8.0 → terminal-table-3.0.2}/lib/terminal-table/table_helper.rb +0 -0
  169. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/COPYING +0 -0
  170. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/_emacs_org.rb +0 -0
  171. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/_handlebars.rb +0 -0
  172. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/_jbuilder.rb +0 -0
  173. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/_org.rb +0 -0
  174. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/asciidoc.rb +0 -0
  175. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/babel.rb +0 -0
  176. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/builder.rb +0 -0
  177. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/cli.rb +0 -0
  178. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/coffee.rb +0 -0
  179. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/commonmarker.rb +0 -0
  180. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/csv.rb +0 -0
  181. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/erb.rb +0 -0
  182. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/erubi.rb +0 -0
  183. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/etanni.rb +0 -0
  184. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/haml.rb +0 -0
  185. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/kramdown.rb +0 -0
  186. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/livescript.rb +0 -0
  187. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/markaby.rb +0 -0
  188. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/nokogiri.rb +0 -0
  189. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/pandoc.rb +0 -0
  190. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/pipeline.rb +0 -0
  191. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/plain.rb +0 -0
  192. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/prawn.rb +0 -0
  193. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/radius.rb +0 -0
  194. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/rdiscount.rb +0 -0
  195. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/redcarpet.rb +0 -0
  196. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/redcloth.rb +0 -0
  197. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/rst-pandoc.rb +0 -0
  198. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/sass.rb +0 -0
  199. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/slim.rb +0 -0
  200. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/string.rb +0 -0
  201. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/typescript.rb +0 -0
  202. /data/bundle/ruby/3.1.0/gems/{tilt-2.4.0 → tilt-2.5.0}/lib/tilt/yajl.rb +0 -0
@@ -0,0 +1,3017 @@
1
+ # encoding: US-ASCII
2
+ # frozen_string_literal: true
3
+ # = csv.rb -- CSV Reading and Writing
4
+ #
5
+ # Created by James Edward Gray II on 2005-10-31.
6
+ #
7
+ # See CSV for documentation.
8
+ #
9
+ # == Description
10
+ #
11
+ # Welcome to the new and improved CSV.
12
+ #
13
+ # This version of the CSV library began its life as FasterCSV. FasterCSV was
14
+ # intended as a replacement to Ruby's then standard CSV library. It was
15
+ # designed to address concerns users of that library had and it had three
16
+ # primary goals:
17
+ #
18
+ # 1. Be significantly faster than CSV while remaining a pure Ruby library.
19
+ # 2. Use a smaller and easier to maintain code base. (FasterCSV eventually
20
+ # grew larger, was also but considerably richer in features. The parsing
21
+ # core remains quite small.)
22
+ # 3. Improve on the CSV interface.
23
+ #
24
+ # Obviously, the last one is subjective. I did try to defer to the original
25
+ # interface whenever I didn't have a compelling reason to change it though, so
26
+ # hopefully this won't be too radically different.
27
+ #
28
+ # We must have met our goals because FasterCSV was renamed to CSV and replaced
29
+ # the original library as of Ruby 1.9. If you are migrating code from 1.8 or
30
+ # earlier, you may have to change your code to comply with the new interface.
31
+ #
32
+ # == What's the Different From the Old CSV?
33
+ #
34
+ # I'm sure I'll miss something, but I'll try to mention most of the major
35
+ # differences I am aware of, to help others quickly get up to speed:
36
+ #
37
+ # === \CSV Parsing
38
+ #
39
+ # * This parser is m17n aware. See CSV for full details.
40
+ # * This library has a stricter parser and will throw MalformedCSVErrors on
41
+ # problematic data.
42
+ # * This library has a less liberal idea of a line ending than CSV. What you
43
+ # set as the <tt>:row_sep</tt> is law. It can auto-detect your line endings
44
+ # though.
45
+ # * The old library returned empty lines as <tt>[nil]</tt>. This library calls
46
+ # them <tt>[]</tt>.
47
+ # * This library has a much faster parser.
48
+ #
49
+ # === Interface
50
+ #
51
+ # * CSV now uses keyword parameters to set options.
52
+ # * CSV no longer has generate_row() or parse_row().
53
+ # * The old CSV's Reader and Writer classes have been dropped.
54
+ # * CSV::open() is now more like Ruby's open().
55
+ # * CSV objects now support most standard IO methods.
56
+ # * CSV now has a new() method used to wrap objects like String and IO for
57
+ # reading and writing.
58
+ # * CSV::generate() is different from the old method.
59
+ # * CSV no longer supports partial reads. It works line-by-line.
60
+ # * CSV no longer allows the instance methods to override the separators for
61
+ # performance reasons. They must be set in the constructor.
62
+ #
63
+ # If you use this library and find yourself missing any functionality I have
64
+ # trimmed, please {let me know}[mailto:james@grayproductions.net].
65
+ #
66
+ # == Documentation
67
+ #
68
+ # See CSV for documentation.
69
+ #
70
+ # == What is CSV, really?
71
+ #
72
+ # CSV maintains a pretty strict definition of CSV taken directly from
73
+ # {the RFC}[https://www.ietf.org/rfc/rfc4180.txt]. I relax the rules in only one
74
+ # place and that is to make using this library easier. CSV will parse all valid
75
+ # CSV.
76
+ #
77
+ # What you don't want to do is to feed CSV invalid data. Because of the way the
78
+ # CSV format works, it's common for a parser to need to read until the end of
79
+ # the file to be sure a field is invalid. This consumes a lot of time and memory.
80
+ #
81
+ # Luckily, when working with invalid CSV, Ruby's built-in methods will almost
82
+ # always be superior in every way. For example, parsing non-quoted fields is as
83
+ # easy as:
84
+ #
85
+ # data.split(",")
86
+ #
87
+ # == Questions and/or Comments
88
+ #
89
+ # Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net]
90
+ # with any questions.
91
+
92
+ require "forwardable"
93
+ require "date"
94
+ require "time"
95
+ require "stringio"
96
+
97
+ require_relative "csv/fields_converter"
98
+ require_relative "csv/input_record_separator"
99
+ require_relative "csv/parser"
100
+ require_relative "csv/row"
101
+ require_relative "csv/table"
102
+ require_relative "csv/writer"
103
+
104
+ # == \CSV
105
+ #
106
+ # === \CSV Data
107
+ #
108
+ # \CSV (comma-separated values) data is a text representation of a table:
109
+ # - A _row_ _separator_ delimits table rows.
110
+ # A common row separator is the newline character <tt>"\n"</tt>.
111
+ # - A _column_ _separator_ delimits fields in a row.
112
+ # A common column separator is the comma character <tt>","</tt>.
113
+ #
114
+ # This \CSV \String, with row separator <tt>"\n"</tt>
115
+ # and column separator <tt>","</tt>,
116
+ # has three rows and two columns:
117
+ # "foo,0\nbar,1\nbaz,2\n"
118
+ #
119
+ # Despite the name \CSV, a \CSV representation can use different separators.
120
+ #
121
+ # For more about tables, see the Wikipedia article
122
+ # "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
123
+ # especially its section
124
+ # "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
125
+ #
126
+ # == \Class \CSV
127
+ #
128
+ # Class \CSV provides methods for:
129
+ # - Parsing \CSV data from a \String object, a \File (via its file path), or an \IO object.
130
+ # - Generating \CSV data to a \String object.
131
+ #
132
+ # To make \CSV available:
133
+ # require 'csv'
134
+ #
135
+ # All examples here assume that this has been done.
136
+ #
137
+ # == Keeping It Simple
138
+ #
139
+ # A \CSV object has dozens of instance methods that offer fine-grained control
140
+ # of parsing and generating \CSV data.
141
+ # For many needs, though, simpler approaches will do.
142
+ #
143
+ # This section summarizes the singleton methods in \CSV
144
+ # that allow you to parse and generate without explicitly
145
+ # creating \CSV objects.
146
+ # For details, follow the links.
147
+ #
148
+ # === Simple Parsing
149
+ #
150
+ # Parsing methods commonly return either of:
151
+ # - An \Array of Arrays of Strings:
152
+ # - The outer \Array is the entire "table".
153
+ # - Each inner \Array is a row.
154
+ # - Each \String is a field.
155
+ # - A CSV::Table object. For details, see
156
+ # {\CSV with Headers}[#class-CSV-label-CSV+with+Headers].
157
+ #
158
+ # ==== Parsing a \String
159
+ #
160
+ # The input to be parsed can be a string:
161
+ # string = "foo,0\nbar,1\nbaz,2\n"
162
+ #
163
+ # \Method CSV.parse returns the entire \CSV data:
164
+ # CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
165
+ #
166
+ # \Method CSV.parse_line returns only the first row:
167
+ # CSV.parse_line(string) # => ["foo", "0"]
168
+ #
169
+ # \CSV extends class \String with instance method String#parse_csv,
170
+ # which also returns only the first row:
171
+ # string.parse_csv # => ["foo", "0"]
172
+ #
173
+ # ==== Parsing Via a \File Path
174
+ #
175
+ # The input to be parsed can be in a file:
176
+ # string = "foo,0\nbar,1\nbaz,2\n"
177
+ # path = 't.csv'
178
+ # File.write(path, string)
179
+ #
180
+ # \Method CSV.read returns the entire \CSV data:
181
+ # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
182
+ #
183
+ # \Method CSV.foreach iterates, passing each row to the given block:
184
+ # CSV.foreach(path) do |row|
185
+ # p row
186
+ # end
187
+ # Output:
188
+ # ["foo", "0"]
189
+ # ["bar", "1"]
190
+ # ["baz", "2"]
191
+ #
192
+ # \Method CSV.table returns the entire \CSV data as a CSV::Table object:
193
+ # CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:3>
194
+ #
195
+ # ==== Parsing from an Open \IO Stream
196
+ #
197
+ # The input to be parsed can be in an open \IO stream:
198
+ #
199
+ # \Method CSV.read returns the entire \CSV data:
200
+ # File.open(path) do |file|
201
+ # CSV.read(file)
202
+ # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
203
+ #
204
+ # As does method CSV.parse:
205
+ # File.open(path) do |file|
206
+ # CSV.parse(file)
207
+ # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
208
+ #
209
+ # \Method CSV.parse_line returns only the first row:
210
+ # File.open(path) do |file|
211
+ # CSV.parse_line(file)
212
+ # end # => ["foo", "0"]
213
+ #
214
+ # \Method CSV.foreach iterates, passing each row to the given block:
215
+ # File.open(path) do |file|
216
+ # CSV.foreach(file) do |row|
217
+ # p row
218
+ # end
219
+ # end
220
+ # Output:
221
+ # ["foo", "0"]
222
+ # ["bar", "1"]
223
+ # ["baz", "2"]
224
+ #
225
+ # \Method CSV.table returns the entire \CSV data as a CSV::Table object:
226
+ # File.open(path) do |file|
227
+ # CSV.table(file)
228
+ # end # => #<CSV::Table mode:col_or_row row_count:3>
229
+ #
230
+ # === Simple Generating
231
+ #
232
+ # \Method CSV.generate returns a \String;
233
+ # this example uses method CSV#<< to append the rows
234
+ # that are to be generated:
235
+ # output_string = CSV.generate do |csv|
236
+ # csv << ['foo', 0]
237
+ # csv << ['bar', 1]
238
+ # csv << ['baz', 2]
239
+ # end
240
+ # output_string # => "foo,0\nbar,1\nbaz,2\n"
241
+ #
242
+ # \Method CSV.generate_line returns a \String containing the single row
243
+ # constructed from an \Array:
244
+ # CSV.generate_line(['foo', '0']) # => "foo,0\n"
245
+ #
246
+ # \CSV extends class \Array with instance method <tt>Array#to_csv</tt>,
247
+ # which forms an \Array into a \String:
248
+ # ['foo', '0'].to_csv # => "foo,0\n"
249
+ #
250
+ # === "Filtering" \CSV
251
+ #
252
+ # \Method CSV.filter provides a Unix-style filter for \CSV data.
253
+ # The input data is processed to form the output data:
254
+ # in_string = "foo,0\nbar,1\nbaz,2\n"
255
+ # out_string = ''
256
+ # CSV.filter(in_string, out_string) do |row|
257
+ # row[0] = row[0].upcase
258
+ # row[1] *= 4
259
+ # end
260
+ # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
261
+ #
262
+ # == \CSV Objects
263
+ #
264
+ # There are three ways to create a \CSV object:
265
+ # - \Method CSV.new returns a new \CSV object.
266
+ # - \Method CSV.instance returns a new or cached \CSV object.
267
+ # - \Method \CSV() also returns a new or cached \CSV object.
268
+ #
269
+ # === Instance Methods
270
+ #
271
+ # \CSV has three groups of instance methods:
272
+ # - Its own internally defined instance methods.
273
+ # - Methods included by module Enumerable.
274
+ # - Methods delegated to class IO. See below.
275
+ #
276
+ # ==== Delegated Methods
277
+ #
278
+ # For convenience, a CSV object will delegate to many methods in class IO.
279
+ # (A few have wrapper "guard code" in \CSV.) You may call:
280
+ # * IO#binmode
281
+ # * #binmode?
282
+ # * IO#close
283
+ # * IO#close_read
284
+ # * IO#close_write
285
+ # * IO#closed?
286
+ # * #eof
287
+ # * #eof?
288
+ # * IO#external_encoding
289
+ # * IO#fcntl
290
+ # * IO#fileno
291
+ # * #flock
292
+ # * IO#flush
293
+ # * IO#fsync
294
+ # * IO#internal_encoding
295
+ # * #ioctl
296
+ # * IO#isatty
297
+ # * #path
298
+ # * IO#pid
299
+ # * IO#pos
300
+ # * IO#pos=
301
+ # * IO#reopen
302
+ # * #rewind
303
+ # * IO#seek
304
+ # * #stat
305
+ # * IO#string
306
+ # * IO#sync
307
+ # * IO#sync=
308
+ # * IO#tell
309
+ # * #to_i
310
+ # * #to_io
311
+ # * IO#truncate
312
+ # * IO#tty?
313
+ #
314
+ # === Options
315
+ #
316
+ # The default values for options are:
317
+ # DEFAULT_OPTIONS = {
318
+ # # For both parsing and generating.
319
+ # col_sep: ",",
320
+ # row_sep: :auto,
321
+ # quote_char: '"',
322
+ # # For parsing.
323
+ # field_size_limit: nil,
324
+ # converters: nil,
325
+ # unconverted_fields: nil,
326
+ # headers: false,
327
+ # return_headers: false,
328
+ # header_converters: nil,
329
+ # skip_blanks: false,
330
+ # skip_lines: nil,
331
+ # liberal_parsing: false,
332
+ # nil_value: nil,
333
+ # empty_value: "",
334
+ # strip: false,
335
+ # # For generating.
336
+ # write_headers: nil,
337
+ # quote_empty: true,
338
+ # force_quotes: false,
339
+ # write_converters: nil,
340
+ # write_nil_value: nil,
341
+ # write_empty_value: "",
342
+ # }
343
+ #
344
+ # ==== Options for Parsing
345
+ #
346
+ # Options for parsing, described in detail below, include:
347
+ # - +row_sep+: Specifies the row separator; used to delimit rows.
348
+ # - +col_sep+: Specifies the column separator; used to delimit fields.
349
+ # - +quote_char+: Specifies the quote character; used to quote fields.
350
+ # - +field_size_limit+: Specifies the maximum field size + 1 allowed.
351
+ # Deprecated since 3.2.3. Use +max_field_size+ instead.
352
+ # - +max_field_size+: Specifies the maximum field size allowed.
353
+ # - +converters+: Specifies the field converters to be used.
354
+ # - +unconverted_fields+: Specifies whether unconverted fields are to be available.
355
+ # - +headers+: Specifies whether data contains headers,
356
+ # or specifies the headers themselves.
357
+ # - +return_headers+: Specifies whether headers are to be returned.
358
+ # - +header_converters+: Specifies the header converters to be used.
359
+ # - +skip_blanks+: Specifies whether blanks lines are to be ignored.
360
+ # - +skip_lines+: Specifies how comments lines are to be recognized.
361
+ # - +strip+: Specifies whether leading and trailing whitespace are to be
362
+ # stripped from fields. This must be compatible with +col_sep+; if it is not,
363
+ # then an +ArgumentError+ exception will be raised.
364
+ # - +liberal_parsing+: Specifies whether \CSV should attempt to parse
365
+ # non-compliant data.
366
+ # - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
367
+ # - +empty_value+: Specifies the object that is to be substituted for each empty field.
368
+ #
369
+ # :include: ../doc/csv/options/common/row_sep.rdoc
370
+ #
371
+ # :include: ../doc/csv/options/common/col_sep.rdoc
372
+ #
373
+ # :include: ../doc/csv/options/common/quote_char.rdoc
374
+ #
375
+ # :include: ../doc/csv/options/parsing/field_size_limit.rdoc
376
+ #
377
+ # :include: ../doc/csv/options/parsing/converters.rdoc
378
+ #
379
+ # :include: ../doc/csv/options/parsing/unconverted_fields.rdoc
380
+ #
381
+ # :include: ../doc/csv/options/parsing/headers.rdoc
382
+ #
383
+ # :include: ../doc/csv/options/parsing/return_headers.rdoc
384
+ #
385
+ # :include: ../doc/csv/options/parsing/header_converters.rdoc
386
+ #
387
+ # :include: ../doc/csv/options/parsing/skip_blanks.rdoc
388
+ #
389
+ # :include: ../doc/csv/options/parsing/skip_lines.rdoc
390
+ #
391
+ # :include: ../doc/csv/options/parsing/strip.rdoc
392
+ #
393
+ # :include: ../doc/csv/options/parsing/liberal_parsing.rdoc
394
+ #
395
+ # :include: ../doc/csv/options/parsing/nil_value.rdoc
396
+ #
397
+ # :include: ../doc/csv/options/parsing/empty_value.rdoc
398
+ #
399
+ # ==== Options for Generating
400
+ #
401
+ # Options for generating, described in detail below, include:
402
+ # - +row_sep+: Specifies the row separator; used to delimit rows.
403
+ # - +col_sep+: Specifies the column separator; used to delimit fields.
404
+ # - +quote_char+: Specifies the quote character; used to quote fields.
405
+ # - +write_headers+: Specifies whether headers are to be written.
406
+ # - +force_quotes+: Specifies whether each output field is to be quoted.
407
+ # - +quote_empty+: Specifies whether each empty output field is to be quoted.
408
+ # - +write_converters+: Specifies the field converters to be used in writing.
409
+ # - +write_nil_value+: Specifies the object that is to be substituted for each +nil+-valued field.
410
+ # - +write_empty_value+: Specifies the object that is to be substituted for each empty field.
411
+ #
412
+ # :include: ../doc/csv/options/common/row_sep.rdoc
413
+ #
414
+ # :include: ../doc/csv/options/common/col_sep.rdoc
415
+ #
416
+ # :include: ../doc/csv/options/common/quote_char.rdoc
417
+ #
418
+ # :include: ../doc/csv/options/generating/write_headers.rdoc
419
+ #
420
+ # :include: ../doc/csv/options/generating/force_quotes.rdoc
421
+ #
422
+ # :include: ../doc/csv/options/generating/quote_empty.rdoc
423
+ #
424
+ # :include: ../doc/csv/options/generating/write_converters.rdoc
425
+ #
426
+ # :include: ../doc/csv/options/generating/write_nil_value.rdoc
427
+ #
428
+ # :include: ../doc/csv/options/generating/write_empty_value.rdoc
429
+ #
430
+ # === \CSV with Headers
431
+ #
432
+ # CSV allows to specify column names of CSV file, whether they are in data, or
433
+ # provided separately. If headers are specified, reading methods return an instance
434
+ # of CSV::Table, consisting of CSV::Row.
435
+ #
436
+ # # Headers are part of data
437
+ # data = CSV.parse(<<~ROWS, headers: true)
438
+ # Name,Department,Salary
439
+ # Bob,Engineering,1000
440
+ # Jane,Sales,2000
441
+ # John,Management,5000
442
+ # ROWS
443
+ #
444
+ # data.class #=> CSV::Table
445
+ # data.first #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000">
446
+ # data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"}
447
+ #
448
+ # # Headers provided by developer
449
+ # data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
450
+ # data.first #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">
451
+ #
452
+ # === \Converters
453
+ #
454
+ # By default, each value (field or header) parsed by \CSV is formed into a \String.
455
+ # You can use a _field_ _converter_ or _header_ _converter_
456
+ # to intercept and modify the parsed values:
457
+ # - See {Field Converters}[#class-CSV-label-Field+Converters].
458
+ # - See {Header Converters}[#class-CSV-label-Header+Converters].
459
+ #
460
+ # Also by default, each value to be written during generation is written 'as-is'.
461
+ # You can use a _write_ _converter_ to modify values before writing.
462
+ # - See {Write Converters}[#class-CSV-label-Write+Converters].
463
+ #
464
+ # ==== Specifying \Converters
465
+ #
466
+ # You can specify converters for parsing or generating in the +options+
467
+ # argument to various \CSV methods:
468
+ # - Option +converters+ for converting parsed field values.
469
+ # - Option +header_converters+ for converting parsed header values.
470
+ # - Option +write_converters+ for converting values to be written (generated).
471
+ #
472
+ # There are three forms for specifying converters:
473
+ # - A converter proc: executable code to be used for conversion.
474
+ # - A converter name: the name of a stored converter.
475
+ # - A converter list: an array of converter procs, converter names, and converter lists.
476
+ #
477
+ # ===== Converter Procs
478
+ #
479
+ # This converter proc, +strip_converter+, accepts a value +field+
480
+ # and returns <tt>field.strip</tt>:
481
+ # strip_converter = proc {|field| field.strip }
482
+ # In this call to <tt>CSV.parse</tt>,
483
+ # the keyword argument <tt>converters: string_converter</tt>
484
+ # specifies that:
485
+ # - \Proc +string_converter+ is to be called for each parsed field.
486
+ # - The converter's return value is to replace the +field+ value.
487
+ # Example:
488
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
489
+ # array = CSV.parse(string, converters: strip_converter)
490
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
491
+ #
492
+ # A converter proc can receive a second argument, +field_info+,
493
+ # that contains details about the field.
494
+ # This modified +strip_converter+ displays its arguments:
495
+ # strip_converter = proc do |field, field_info|
496
+ # p [field, field_info]
497
+ # field.strip
498
+ # end
499
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
500
+ # array = CSV.parse(string, converters: strip_converter)
501
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
502
+ # Output:
503
+ # [" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
504
+ # [" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
505
+ # [" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
506
+ # [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
507
+ # [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
508
+ # [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
509
+ # Each CSV::FieldInfo object shows:
510
+ # - The 0-based field index.
511
+ # - The 1-based line index.
512
+ # - The field header, if any.
513
+ #
514
+ # ===== Stored \Converters
515
+ #
516
+ # A converter may be given a name and stored in a structure where
517
+ # the parsing methods can find it by name.
518
+ #
519
+ # The storage structure for field converters is the \Hash CSV::Converters.
520
+ # It has several built-in converter procs:
521
+ # - <tt>:integer</tt>: converts each \String-embedded integer into a true \Integer.
522
+ # - <tt>:float</tt>: converts each \String-embedded float into a true \Float.
523
+ # - <tt>:date</tt>: converts each \String-embedded date into a true \Date.
524
+ # - <tt>:date_time</tt>: converts each \String-embedded date-time into a true \DateTime
525
+ # - <tt>:time</tt>: converts each \String-embedded time into a true \Time
526
+ # .
527
+ # This example creates a converter proc, then stores it:
528
+ # strip_converter = proc {|field| field.strip }
529
+ # CSV::Converters[:strip] = strip_converter
530
+ # Then the parsing method call can refer to the converter
531
+ # by its name, <tt>:strip</tt>:
532
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
533
+ # array = CSV.parse(string, converters: :strip)
534
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
535
+ #
536
+ # The storage structure for header converters is the \Hash CSV::HeaderConverters,
537
+ # which works in the same way.
538
+ # It also has built-in converter procs:
539
+ # - <tt>:downcase</tt>: Downcases each header.
540
+ # - <tt>:symbol</tt>: Converts each header to a \Symbol.
541
+ #
542
+ # There is no such storage structure for write headers.
543
+ #
544
+ # In order for the parsing methods to access stored converters in non-main-Ractors, the
545
+ # storage structure must be made shareable first.
546
+ # Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
547
+ # <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
548
+ # of Ractors that use the converters stored in these structures. (Since making the storage
549
+ # structures shareable involves freezing them, any custom converters that are to be used
550
+ # must be added first.)
551
+ #
552
+ # ===== Converter Lists
553
+ #
554
+ # A _converter_ _list_ is an \Array that may include any assortment of:
555
+ # - Converter procs.
556
+ # - Names of stored converters.
557
+ # - Nested converter lists.
558
+ #
559
+ # Examples:
560
+ # numeric_converters = [:integer, :float]
561
+ # date_converters = [:date, :date_time]
562
+ # [numeric_converters, strip_converter]
563
+ # [strip_converter, date_converters, :float]
564
+ #
565
+ # Like a converter proc, a converter list may be named and stored in either
566
+ # \CSV::Converters or CSV::HeaderConverters:
567
+ # CSV::Converters[:custom] = [strip_converter, date_converters, :float]
568
+ # CSV::HeaderConverters[:custom] = [:downcase, :symbol]
569
+ #
570
+ # There are two built-in converter lists:
571
+ # CSV::Converters[:numeric] # => [:integer, :float]
572
+ # CSV::Converters[:all] # => [:date_time, :numeric]
573
+ #
574
+ # ==== Field \Converters
575
+ #
576
+ # With no conversion, all parsed fields in all rows become Strings:
577
+ # string = "foo,0\nbar,1\nbaz,2\n"
578
+ # ary = CSV.parse(string)
579
+ # ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
580
+ #
581
+ # When you specify a field converter, each parsed field is passed to the converter;
582
+ # its return value becomes the stored value for the field.
583
+ # A converter might, for example, convert an integer embedded in a \String
584
+ # into a true \Integer.
585
+ # (In fact, that's what built-in field converter +:integer+ does.)
586
+ #
587
+ # There are three ways to use field \converters.
588
+ #
589
+ # - Using option {converters}[#class-CSV-label-Option+converters] with a parsing method:
590
+ # ary = CSV.parse(string, converters: :integer)
591
+ # ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
592
+ # - Using option {converters}[#class-CSV-label-Option+converters] with a new \CSV instance:
593
+ # csv = CSV.new(string, converters: :integer)
594
+ # # Field converters in effect:
595
+ # csv.converters # => [:integer]
596
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
597
+ # - Using method #convert to add a field converter to a \CSV instance:
598
+ # csv = CSV.new(string)
599
+ # # Add a converter.
600
+ # csv.convert(:integer)
601
+ # csv.converters # => [:integer]
602
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
603
+ #
604
+ # Installing a field converter does not affect already-read rows:
605
+ # csv = CSV.new(string)
606
+ # csv.shift # => ["foo", "0"]
607
+ # # Add a converter.
608
+ # csv.convert(:integer)
609
+ # csv.converters # => [:integer]
610
+ # csv.read # => [["bar", 1], ["baz", 2]]
611
+ #
612
+ # There are additional built-in \converters, and custom \converters are also supported.
613
+ #
614
+ # ===== Built-In Field \Converters
615
+ #
616
+ # The built-in field converters are in \Hash CSV::Converters:
617
+ # - Each key is a field converter name.
618
+ # - Each value is one of:
619
+ # - A \Proc field converter.
620
+ # - An \Array of field converter names.
621
+ #
622
+ # Display:
623
+ # CSV::Converters.each_pair do |name, value|
624
+ # if value.kind_of?(Proc)
625
+ # p [name, value.class]
626
+ # else
627
+ # p [name, value]
628
+ # end
629
+ # end
630
+ # Output:
631
+ # [:integer, Proc]
632
+ # [:float, Proc]
633
+ # [:numeric, [:integer, :float]]
634
+ # [:date, Proc]
635
+ # [:date_time, Proc]
636
+ # [:time, Proc]
637
+ # [:all, [:date_time, :numeric]]
638
+ #
639
+ # Each of these converters transcodes values to UTF-8 before attempting conversion.
640
+ # If a value cannot be transcoded to UTF-8 the conversion will
641
+ # fail and the value will remain unconverted.
642
+ #
643
+ # Converter +:integer+ converts each field that Integer() accepts:
644
+ # data = '0,1,2,x'
645
+ # # Without the converter
646
+ # csv = CSV.parse_line(data)
647
+ # csv # => ["0", "1", "2", "x"]
648
+ # # With the converter
649
+ # csv = CSV.parse_line(data, converters: :integer)
650
+ # csv # => [0, 1, 2, "x"]
651
+ #
652
+ # Converter +:float+ converts each field that Float() accepts:
653
+ # data = '1.0,3.14159,x'
654
+ # # Without the converter
655
+ # csv = CSV.parse_line(data)
656
+ # csv # => ["1.0", "3.14159", "x"]
657
+ # # With the converter
658
+ # csv = CSV.parse_line(data, converters: :float)
659
+ # csv # => [1.0, 3.14159, "x"]
660
+ #
661
+ # Converter +:numeric+ converts with both +:integer+ and +:float+..
662
+ #
663
+ # Converter +:date+ converts each field that Date::parse accepts:
664
+ # data = '2001-02-03,x'
665
+ # # Without the converter
666
+ # csv = CSV.parse_line(data)
667
+ # csv # => ["2001-02-03", "x"]
668
+ # # With the converter
669
+ # csv = CSV.parse_line(data, converters: :date)
670
+ # csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]
671
+ #
672
+ # Converter +:date_time+ converts each field that DateTime::parse accepts:
673
+ # data = '2020-05-07T14:59:00-05:00,x'
674
+ # # Without the converter
675
+ # csv = CSV.parse_line(data)
676
+ # csv # => ["2020-05-07T14:59:00-05:00", "x"]
677
+ # # With the converter
678
+ # csv = CSV.parse_line(data, converters: :date_time)
679
+ # csv # => [#<DateTime: 2020-05-07T14:59:00-05:00 ((2458977j,71940s,0n),-18000s,2299161j)>, "x"]
680
+ #
681
+ # Converter +time+ converts each field that Time::parse accepts:
682
+ # data = '2020-05-07T14:59:00-05:00,x'
683
+ # # Without the converter
684
+ # csv = CSV.parse_line(data)
685
+ # csv # => ["2020-05-07T14:59:00-05:00", "x"]
686
+ # # With the converter
687
+ # csv = CSV.parse_line(data, converters: :time)
688
+ # csv # => [2020-05-07 14:59:00 -0500, "x"]
689
+ #
690
+ # Converter +:numeric+ converts with both +:date_time+ and +:numeric+..
691
+ #
692
+ # As seen above, method #convert adds \converters to a \CSV instance,
693
+ # and method #converters returns an \Array of the \converters in effect:
694
+ # csv = CSV.new('0,1,2')
695
+ # csv.converters # => []
696
+ # csv.convert(:integer)
697
+ # csv.converters # => [:integer]
698
+ # csv.convert(:date)
699
+ # csv.converters # => [:integer, :date]
700
+ #
701
+ # ===== Custom Field \Converters
702
+ #
703
+ # You can define a custom field converter:
704
+ # strip_converter = proc {|field| field.strip }
705
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
706
+ # array = CSV.parse(string, converters: strip_converter)
707
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
708
+ # You can register the converter in \Converters \Hash,
709
+ # which allows you to refer to it by name:
710
+ # CSV::Converters[:strip] = strip_converter
711
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
712
+ # array = CSV.parse(string, converters: :strip)
713
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
714
+ #
715
+ # ==== Header \Converters
716
+ #
717
+ # Header converters operate only on headers (and not on other rows).
718
+ #
719
+ # There are three ways to use header \converters;
720
+ # these examples use built-in header converter +:downcase+,
721
+ # which downcases each parsed header.
722
+ #
723
+ # - Option +header_converters+ with a singleton parsing method:
724
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
725
+ # tbl = CSV.parse(string, headers: true, header_converters: :downcase)
726
+ # tbl.class # => CSV::Table
727
+ # tbl.headers # => ["name", "count"]
728
+ #
729
+ # - Option +header_converters+ with a new \CSV instance:
730
+ # csv = CSV.new(string, header_converters: :downcase)
731
+ # # Header converters in effect:
732
+ # csv.header_converters # => [:downcase]
733
+ # tbl = CSV.parse(string, headers: true)
734
+ # tbl.headers # => ["Name", "Count"]
735
+ #
736
+ # - Method #header_convert adds a header converter to a \CSV instance:
737
+ # csv = CSV.new(string)
738
+ # # Add a header converter.
739
+ # csv.header_convert(:downcase)
740
+ # csv.header_converters # => [:downcase]
741
+ # tbl = CSV.parse(string, headers: true)
742
+ # tbl.headers # => ["Name", "Count"]
743
+ #
744
+ # ===== Built-In Header \Converters
745
+ #
746
+ # The built-in header \converters are in \Hash CSV::HeaderConverters.
747
+ # The keys there are the names of the \converters:
748
+ # CSV::HeaderConverters.keys # => [:downcase, :symbol]
749
+ #
750
+ # Converter +:downcase+ converts each header by downcasing it:
751
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
752
+ # tbl = CSV.parse(string, headers: true, header_converters: :downcase)
753
+ # tbl.class # => CSV::Table
754
+ # tbl.headers # => ["name", "count"]
755
+ #
756
+ # Converter +:symbol+ converts each header by making it into a \Symbol:
757
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
758
+ # tbl = CSV.parse(string, headers: true, header_converters: :symbol)
759
+ # tbl.headers # => [:name, :count]
760
+ # Details:
761
+ # - Strips leading and trailing whitespace.
762
+ # - Downcases the header.
763
+ # - Replaces embedded spaces with underscores.
764
+ # - Removes non-word characters.
765
+ # - Makes the string into a \Symbol.
766
+ #
767
+ # ===== Custom Header \Converters
768
+ #
769
+ # You can define a custom header converter:
770
+ # upcase_converter = proc {|header| header.upcase }
771
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
772
+ # table = CSV.parse(string, headers: true, header_converters: upcase_converter)
773
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
774
+ # table.headers # => ["NAME", "VALUE"]
775
+ # You can register the converter in \HeaderConverters \Hash,
776
+ # which allows you to refer to it by name:
777
+ # CSV::HeaderConverters[:upcase] = upcase_converter
778
+ # table = CSV.parse(string, headers: true, header_converters: :upcase)
779
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
780
+ # table.headers # => ["NAME", "VALUE"]
781
+ #
782
+ # ===== Write \Converters
783
+ #
784
+ # When you specify a write converter for generating \CSV,
785
+ # each field to be written is passed to the converter;
786
+ # its return value becomes the new value for the field.
787
+ # A converter might, for example, strip whitespace from a field.
788
+ #
789
+ # Using no write converter (all fields unmodified):
790
+ # output_string = CSV.generate do |csv|
791
+ # csv << [' foo ', 0]
792
+ # csv << [' bar ', 1]
793
+ # csv << [' baz ', 2]
794
+ # end
795
+ # output_string # => " foo ,0\n bar ,1\n baz ,2\n"
796
+ # Using option +write_converters+ with two custom write converters:
797
+ # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
798
+ # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
799
+ # write_converters = [strip_converter, upcase_converter]
800
+ # output_string = CSV.generate(write_converters: write_converters) do |csv|
801
+ # csv << [' foo ', 0]
802
+ # csv << [' bar ', 1]
803
+ # csv << [' baz ', 2]
804
+ # end
805
+ # output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
806
+ #
807
+ # === Character Encodings (M17n or Multilingualization)
808
+ #
809
+ # This new CSV parser is m17n savvy. The parser works in the Encoding of the IO
810
+ # or String object being read from or written to. Your data is never transcoded
811
+ # (unless you ask Ruby to transcode it for you) and will literally be parsed in
812
+ # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
813
+ # Encoding of your data. This is accomplished by transcoding the parser itself
814
+ # into your Encoding.
815
+ #
816
+ # Some transcoding must take place, of course, to accomplish this multiencoding
817
+ # support. For example, <tt>:col_sep</tt>, <tt>:row_sep</tt>, and
818
+ # <tt>:quote_char</tt> must be transcoded to match your data. Hopefully this
819
+ # makes the entire process feel transparent, since CSV's defaults should just
820
+ # magically work for your data. However, you can set these values manually in
821
+ # the target Encoding to avoid the translation.
822
+ #
823
+ # It's also important to note that while all of CSV's core parser is now
824
+ # Encoding agnostic, some features are not. For example, the built-in
825
+ # converters will try to transcode data to UTF-8 before making conversions.
826
+ # Again, you can provide custom converters that are aware of your Encodings to
827
+ # avoid this translation. It's just too hard for me to support native
828
+ # conversions in all of Ruby's Encodings.
829
+ #
830
+ # Anyway, the practical side of this is simple: make sure IO and String objects
831
+ # passed into CSV have the proper Encoding set and everything should just work.
832
+ # CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(),
833
+ # CSV::read(), and CSV::readlines()) do allow you to specify the Encoding.
834
+ #
835
+ # One minor exception comes when generating CSV into a String with an Encoding
836
+ # that is not ASCII compatible. There's no existing data for CSV to use to
837
+ # prepare itself and thus you will probably need to manually specify the desired
838
+ # Encoding for most of those cases. It will try to guess using the fields in a
839
+ # row of output though, when using CSV::generate_line() or Array#to_csv().
840
+ #
841
+ # I try to point out any other Encoding issues in the documentation of methods
842
+ # as they come up.
843
+ #
844
+ # This has been tested to the best of my ability with all non-"dummy" Encodings
845
+ # Ruby ships with. However, it is brave new code and may have some bugs.
846
+ # Please feel free to {report}[mailto:james@grayproductions.net] any issues you
847
+ # find with it.
848
+ #
849
+ class CSV
850
+
851
+ # The error thrown when the parser encounters illegal CSV formatting.
852
+ class MalformedCSVError < RuntimeError
853
+ attr_reader :line_number
854
+ alias_method :lineno, :line_number
855
+ def initialize(message, line_number)
856
+ @line_number = line_number
857
+ super("#{message} in line #{line_number}.")
858
+ end
859
+ end
860
+
861
+ # The error thrown when the parser encounters invalid encoding in CSV.
862
+ class InvalidEncodingError < MalformedCSVError
863
+ attr_reader :encoding
864
+ def initialize(encoding, line_number)
865
+ @encoding = encoding
866
+ super("Invalid byte sequence in #{encoding}", line_number)
867
+ end
868
+ end
869
+
870
+ #
871
+ # A FieldInfo Struct contains details about a field's position in the data
872
+ # source it was read from. CSV will pass this Struct to some blocks that make
873
+ # decisions based on field structure. See CSV.convert_fields() for an
874
+ # example.
875
+ #
876
+ # <b><tt>index</tt></b>:: The zero-based index of the field in its row.
877
+ # <b><tt>line</tt></b>:: The line of the data source this row is from.
878
+ # <b><tt>header</tt></b>:: The header for the column, when available.
879
+ # <b><tt>quoted?</tt></b>:: True or false, whether the original value is quoted or not.
880
+ #
881
+ FieldInfo = Struct.new(:index, :line, :header, :quoted?)
882
+
883
+ # A Regexp used to find and convert some common Date formats.
884
+ DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} |
885
+ \d{4}-\d{2}-\d{2} )\z /x
886
+ # A Regexp used to find and convert some common (Date)Time formats.
887
+ DateTimeMatcher =
888
+ / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} |
889
+ # ISO-8601 and RFC-3339 (space instead of T) recognized by (Date)Time.parse
890
+ \d{4}-\d{2}-\d{2}
891
+ (?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
892
+ )\z /x
893
+
894
+ # The encoding used by all converters.
895
+ ConverterEncoding = Encoding.find("UTF-8")
896
+
897
+ # A \Hash containing the names and \Procs for the built-in field converters.
898
+ # See {Built-In Field Converters}[#class-CSV-label-Built-In+Field+Converters].
899
+ #
900
+ # This \Hash is intentionally left unfrozen, and may be extended with
901
+ # custom field converters.
902
+ # See {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters].
903
+ Converters = {
904
+ integer: lambda { |f|
905
+ Integer(f.encode(ConverterEncoding)) rescue f
906
+ },
907
+ float: lambda { |f|
908
+ Float(f.encode(ConverterEncoding)) rescue f
909
+ },
910
+ numeric: [:integer, :float],
911
+ date: lambda { |f|
912
+ begin
913
+ e = f.encode(ConverterEncoding)
914
+ e.match?(DateMatcher) ? Date.parse(e) : f
915
+ rescue # encoding conversion or date parse errors
916
+ f
917
+ end
918
+ },
919
+ date_time: lambda { |f|
920
+ begin
921
+ e = f.encode(ConverterEncoding)
922
+ e.match?(DateTimeMatcher) ? DateTime.parse(e) : f
923
+ rescue # encoding conversion or date parse errors
924
+ f
925
+ end
926
+ },
927
+ time: lambda { |f|
928
+ begin
929
+ e = f.encode(ConverterEncoding)
930
+ e.match?(DateTimeMatcher) ? Time.parse(e) : f
931
+ rescue # encoding conversion or parse errors
932
+ f
933
+ end
934
+ },
935
+ all: [:date_time, :numeric],
936
+ }
937
+
938
+ # A \Hash containing the names and \Procs for the built-in header converters.
939
+ # See {Built-In Header Converters}[#class-CSV-label-Built-In+Header+Converters].
940
+ #
941
+ # This \Hash is intentionally left unfrozen, and may be extended with
942
+ # custom field converters.
943
+ # See {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters].
944
+ HeaderConverters = {
945
+ downcase: lambda { |h| h.encode(ConverterEncoding).downcase },
946
+ symbol: lambda { |h|
947
+ h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip.
948
+ gsub(/\s+/, "_").to_sym
949
+ },
950
+ symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym }
951
+ }
952
+
953
+ # Default values for method options.
954
+ DEFAULT_OPTIONS = {
955
+ # For both parsing and generating.
956
+ col_sep: ",",
957
+ row_sep: :auto,
958
+ quote_char: '"',
959
+ # For parsing.
960
+ field_size_limit: nil,
961
+ max_field_size: nil,
962
+ converters: nil,
963
+ unconverted_fields: nil,
964
+ headers: false,
965
+ return_headers: false,
966
+ header_converters: nil,
967
+ skip_blanks: false,
968
+ skip_lines: nil,
969
+ liberal_parsing: false,
970
+ nil_value: nil,
971
+ empty_value: "",
972
+ strip: false,
973
+ # For generating.
974
+ write_headers: nil,
975
+ quote_empty: true,
976
+ force_quotes: false,
977
+ write_converters: nil,
978
+ write_nil_value: nil,
979
+ write_empty_value: "",
980
+ }.freeze
981
+
982
+ class << self
983
+ # :call-seq:
984
+ # instance(string, **options)
985
+ # instance(io = $stdout, **options)
986
+ # instance(string, **options) {|csv| ... }
987
+ # instance(io = $stdout, **options) {|csv| ... }
988
+ #
989
+ # Creates or retrieves cached \CSV objects.
990
+ # For arguments and options, see CSV.new.
991
+ #
992
+ # This API is not Ractor-safe.
993
+ #
994
+ # ---
995
+ #
996
+ # With no block given, returns a \CSV object.
997
+ #
998
+ # The first call to +instance+ creates and caches a \CSV object:
999
+ # s0 = 's0'
1000
+ # csv0 = CSV.instance(s0)
1001
+ # csv0.class # => CSV
1002
+ #
1003
+ # Subsequent calls to +instance+ with that _same_ +string+ or +io+
1004
+ # retrieve that same cached object:
1005
+ # csv1 = CSV.instance(s0)
1006
+ # csv1.class # => CSV
1007
+ # csv1.equal?(csv0) # => true # Same CSV object
1008
+ #
1009
+ # A subsequent call to +instance+ with a _different_ +string+ or +io+
1010
+ # creates and caches a _different_ \CSV object.
1011
+ # s1 = 's1'
1012
+ # csv2 = CSV.instance(s1)
1013
+ # csv2.equal?(csv0) # => false # Different CSV object
1014
+ #
1015
+ # All the cached objects remains available:
1016
+ # csv3 = CSV.instance(s0)
1017
+ # csv3.equal?(csv0) # true # Same CSV object
1018
+ # csv4 = CSV.instance(s1)
1019
+ # csv4.equal?(csv2) # true # Same CSV object
1020
+ #
1021
+ # ---
1022
+ #
1023
+ # When a block is given, calls the block with the created or retrieved
1024
+ # \CSV object; returns the block's return value:
1025
+ # CSV.instance(s0) {|csv| :foo } # => :foo
1026
+ def instance(data = $stdout, **options)
1027
+ # create a _signature_ for this method call, data object and options
1028
+ sig = [data.object_id] +
1029
+ options.values_at(*DEFAULT_OPTIONS.keys)
1030
+
1031
+ # fetch or create the instance for this signature
1032
+ @@instances ||= Hash.new
1033
+ instance = (@@instances[sig] ||= new(data, **options))
1034
+
1035
+ if block_given?
1036
+ yield instance # run block, if given, returning result
1037
+ else
1038
+ instance # or return the instance
1039
+ end
1040
+ end
1041
+
1042
+ # :call-seq:
1043
+ # filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1044
+ # filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1045
+ # filter(**options) {|row| ... } -> array_of_arrays or csv_table
1046
+ #
1047
+ # - Parses \CSV from a source (\String, \IO stream, or ARGF).
1048
+ # - Calls the given block with each parsed row:
1049
+ # - Without headers, each row is an \Array.
1050
+ # - With headers, each row is a CSV::Row.
1051
+ # - Generates \CSV to an output (\String, \IO stream, or STDOUT).
1052
+ # - Returns the parsed source:
1053
+ # - Without headers, an \Array of \Arrays.
1054
+ # - With headers, a CSV::Table.
1055
+ #
1056
+ # When +in_string_or_io+ is given, but not +out_string_or_io+,
1057
+ # parses from the given +in_string_or_io+
1058
+ # and generates to STDOUT.
1059
+ #
1060
+ # \String input without headers:
1061
+ #
1062
+ # in_string = "foo,0\nbar,1\nbaz,2"
1063
+ # CSV.filter(in_string) do |row|
1064
+ # row[0].upcase!
1065
+ # row[1] = - row[1].to_i
1066
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1067
+ #
1068
+ # Output (to STDOUT):
1069
+ #
1070
+ # FOO,0
1071
+ # BAR,-1
1072
+ # BAZ,-2
1073
+ #
1074
+ # \String input with headers:
1075
+ #
1076
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1077
+ # CSV.filter(in_string, headers: true) do |row|
1078
+ # row[0].upcase!
1079
+ # row[1] = - row[1].to_i
1080
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1081
+ #
1082
+ # Output (to STDOUT):
1083
+ #
1084
+ # Name,Value
1085
+ # FOO,0
1086
+ # BAR,-1
1087
+ # BAZ,-2
1088
+ #
1089
+ # \IO stream input without headers:
1090
+ #
1091
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1092
+ # File.open('t.csv') do |in_io|
1093
+ # CSV.filter(in_io) do |row|
1094
+ # row[0].upcase!
1095
+ # row[1] = - row[1].to_i
1096
+ # end
1097
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1098
+ #
1099
+ # Output (to STDOUT):
1100
+ #
1101
+ # FOO,0
1102
+ # BAR,-1
1103
+ # BAZ,-2
1104
+ #
1105
+ # \IO stream input with headers:
1106
+ #
1107
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1108
+ # File.open('t.csv') do |in_io|
1109
+ # CSV.filter(in_io, headers: true) do |row|
1110
+ # row[0].upcase!
1111
+ # row[1] = - row[1].to_i
1112
+ # end
1113
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1114
+ #
1115
+ # Output (to STDOUT):
1116
+ #
1117
+ # Name,Value
1118
+ # FOO,0
1119
+ # BAR,-1
1120
+ # BAZ,-2
1121
+ #
1122
+ # When both +in_string_or_io+ and +out_string_or_io+ are given,
1123
+ # parses from +in_string_or_io+ and generates to +out_string_or_io+.
1124
+ #
1125
+ # \String output without headers:
1126
+ #
1127
+ # in_string = "foo,0\nbar,1\nbaz,2"
1128
+ # out_string = ''
1129
+ # CSV.filter(in_string, out_string) do |row|
1130
+ # row[0].upcase!
1131
+ # row[1] = - row[1].to_i
1132
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1133
+ # out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1134
+ #
1135
+ # \String output with headers:
1136
+ #
1137
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1138
+ # out_string = ''
1139
+ # CSV.filter(in_string, out_string, headers: true) do |row|
1140
+ # row[0].upcase!
1141
+ # row[1] = - row[1].to_i
1142
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1143
+ # out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1144
+ #
1145
+ # \IO stream output without headers:
1146
+ #
1147
+ # in_string = "foo,0\nbar,1\nbaz,2"
1148
+ # File.open('t.csv', 'w') do |out_io|
1149
+ # CSV.filter(in_string, out_io) do |row|
1150
+ # row[0].upcase!
1151
+ # row[1] = - row[1].to_i
1152
+ # end
1153
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1154
+ # File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1155
+ #
1156
+ # \IO stream output with headers:
1157
+ #
1158
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1159
+ # File.open('t.csv', 'w') do |out_io|
1160
+ # CSV.filter(in_string, out_io, headers: true) do |row|
1161
+ # row[0].upcase!
1162
+ # row[1] = - row[1].to_i
1163
+ # end
1164
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1165
+ # File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1166
+ #
1167
+ # When neither +in_string_or_io+ nor +out_string_or_io+ given,
1168
+ # parses from {ARGF}[rdoc-ref:ARGF]
1169
+ # and generates to STDOUT.
1170
+ #
1171
+ # Without headers:
1172
+ #
1173
+ # # Put Ruby code into a file.
1174
+ # ruby = <<-EOT
1175
+ # require 'csv'
1176
+ # CSV.filter do |row|
1177
+ # row[0].upcase!
1178
+ # row[1] = - row[1].to_i
1179
+ # end
1180
+ # EOT
1181
+ # File.write('t.rb', ruby)
1182
+ # # Put some CSV into a file.
1183
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1184
+ # # Run the Ruby code with CSV filename as argument.
1185
+ # system(Gem.ruby, "t.rb", "t.csv")
1186
+ #
1187
+ # Output (to STDOUT):
1188
+ #
1189
+ # FOO,0
1190
+ # BAR,-1
1191
+ # BAZ,-2
1192
+ #
1193
+ # With headers:
1194
+ #
1195
+ # # Put Ruby code into a file.
1196
+ # ruby = <<-EOT
1197
+ # require 'csv'
1198
+ # CSV.filter(headers: true) do |row|
1199
+ # row[0].upcase!
1200
+ # row[1] = - row[1].to_i
1201
+ # end
1202
+ # EOT
1203
+ # File.write('t.rb', ruby)
1204
+ # # Put some CSV into a file.
1205
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1206
+ # # Run the Ruby code with CSV filename as argument.
1207
+ # system(Gem.ruby, "t.rb", "t.csv")
1208
+ #
1209
+ # Output (to STDOUT):
1210
+ #
1211
+ # Name,Value
1212
+ # FOO,0
1213
+ # BAR,-1
1214
+ # BAZ,-2
1215
+ #
1216
+ # Arguments:
1217
+ #
1218
+ # * Argument +in_string_or_io+ must be a \String or an \IO stream.
1219
+ # * Argument +out_string_or_io+ must be a \String or an \IO stream.
1220
+ # * Arguments <tt>**options</tt> must be keyword options.
1221
+ #
1222
+ # - Each option defined as an {option for parsing}[#class-CSV-label-Options+for+Parsing]
1223
+ # is used for parsing the filter input.
1224
+ # - Each option defined as an {option for generating}[#class-CSV-label-Options+for+Generating]
1225
+ # is used for generator the filter input.
1226
+ #
1227
+ # However, there are three options that may be used for both parsing and generating:
1228
+ # +col_sep+, +quote_char+, and +row_sep+.
1229
+ #
1230
+ # Therefore for method +filter+ (and method +filter+ only),
1231
+ # there are special options that allow these parsing and generating options
1232
+ # to be specified separately:
1233
+ #
1234
+ # - Options +input_col_sep+ and +output_col_sep+
1235
+ # (and their aliases +in_col_sep+ and +out_col_sep+)
1236
+ # specify the column separators for parsing and generating.
1237
+ # - Options +input_quote_char+ and +output_quote_char+
1238
+ # (and their aliases +in_quote_char+ and +out_quote_char+)
1239
+ # specify the quote characters for parsing and generting.
1240
+ # - Options +input_row_sep+ and +output_row_sep+
1241
+ # (and their aliases +in_row_sep+ and +out_row_sep+)
1242
+ # specify the row separators for parsing and generating.
1243
+ #
1244
+ # Example options (for column separators):
1245
+ #
1246
+ # CSV.filter # Default for both parsing and generating.
1247
+ # CSV.filter(in_col_sep: ';') # ';' for parsing, default for generating.
1248
+ # CSV.filter(out_col_sep: '|') # Default for parsing, '|' for generating.
1249
+ # CSV.filter(in_col_sep: ';', out_col_sep: '|') # ';' for parsing, '|' for generating.
1250
+ #
1251
+ # Note that for a special option (e.g., +input_col_sep+)
1252
+ # and its corresponding "regular" option (e.g., +col_sep+),
1253
+ # the two are mutually overriding.
1254
+ #
1255
+ # Another example (possibly surprising):
1256
+ #
1257
+ # CSV.filter(in_col_sep: ';', col_sep: '|') # '|' for both parsing(!) and generating.
1258
+ #
1259
+ def filter(input=nil, output=nil, **options)
1260
+ # parse options for input, output, or both
1261
+ in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
1262
+ options.each do |key, value|
1263
+ case key
1264
+ when /\Ain(?:put)?_(.+)\Z/
1265
+ in_options[$1.to_sym] = value
1266
+ when /\Aout(?:put)?_(.+)\Z/
1267
+ out_options[$1.to_sym] = value
1268
+ else
1269
+ in_options[key] = value
1270
+ out_options[key] = value
1271
+ end
1272
+ end
1273
+
1274
+ # build input and output wrappers
1275
+ input = new(input || ARGF, **in_options)
1276
+ output = new(output || $stdout, **out_options)
1277
+
1278
+ # process headers
1279
+ need_manual_header_output =
1280
+ (in_options[:headers] and
1281
+ out_options[:headers] == true and
1282
+ out_options[:write_headers])
1283
+ if need_manual_header_output
1284
+ first_row = input.shift
1285
+ if first_row
1286
+ if first_row.is_a?(Row)
1287
+ headers = first_row.headers
1288
+ yield headers
1289
+ output << headers
1290
+ end
1291
+ yield first_row
1292
+ output << first_row
1293
+ end
1294
+ end
1295
+
1296
+ # read, yield, write
1297
+ input.each do |row|
1298
+ yield row
1299
+ output << row
1300
+ end
1301
+ end
1302
+
1303
+ #
1304
+ # :call-seq:
1305
+ # foreach(path_or_io, mode='r', **options) {|row| ... )
1306
+ # foreach(path_or_io, mode='r', **options) -> new_enumerator
1307
+ #
1308
+ # Calls the block with each row read from source +path_or_io+.
1309
+ #
1310
+ # \Path input without headers:
1311
+ #
1312
+ # string = "foo,0\nbar,1\nbaz,2\n"
1313
+ # in_path = 't.csv'
1314
+ # File.write(in_path, string)
1315
+ # CSV.foreach(in_path) {|row| p row }
1316
+ #
1317
+ # Output:
1318
+ #
1319
+ # ["foo", "0"]
1320
+ # ["bar", "1"]
1321
+ # ["baz", "2"]
1322
+ #
1323
+ # \Path input with headers:
1324
+ #
1325
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1326
+ # in_path = 't.csv'
1327
+ # File.write(in_path, string)
1328
+ # CSV.foreach(in_path, headers: true) {|row| p row }
1329
+ #
1330
+ # Output:
1331
+ #
1332
+ # <CSV::Row "Name":"foo" "Value":"0">
1333
+ # <CSV::Row "Name":"bar" "Value":"1">
1334
+ # <CSV::Row "Name":"baz" "Value":"2">
1335
+ #
1336
+ # \IO stream input without headers:
1337
+ #
1338
+ # string = "foo,0\nbar,1\nbaz,2\n"
1339
+ # path = 't.csv'
1340
+ # File.write(path, string)
1341
+ # File.open('t.csv') do |in_io|
1342
+ # CSV.foreach(in_io) {|row| p row }
1343
+ # end
1344
+ #
1345
+ # Output:
1346
+ #
1347
+ # ["foo", "0"]
1348
+ # ["bar", "1"]
1349
+ # ["baz", "2"]
1350
+ #
1351
+ # \IO stream input with headers:
1352
+ #
1353
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1354
+ # path = 't.csv'
1355
+ # File.write(path, string)
1356
+ # File.open('t.csv') do |in_io|
1357
+ # CSV.foreach(in_io, headers: true) {|row| p row }
1358
+ # end
1359
+ #
1360
+ # Output:
1361
+ #
1362
+ # <CSV::Row "Name":"foo" "Value":"0">
1363
+ # <CSV::Row "Name":"bar" "Value":"1">
1364
+ # <CSV::Row "Name":"baz" "Value":"2">
1365
+ #
1366
+ # With no block given, returns an \Enumerator:
1367
+ #
1368
+ # string = "foo,0\nbar,1\nbaz,2\n"
1369
+ # path = 't.csv'
1370
+ # File.write(path, string)
1371
+ # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
1372
+ #
1373
+ # Arguments:
1374
+ # * Argument +path_or_io+ must be a file path or an \IO stream.
1375
+ # * Argument +mode+, if given, must be a \File mode.
1376
+ # See {Access Modes}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Access+Modes].
1377
+ # * Arguments <tt>**options</tt> must be keyword options.
1378
+ # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1379
+ # * This method optionally accepts an additional <tt>:encoding</tt> option
1380
+ # that you can use to specify the Encoding of the data read from +path+ or +io+.
1381
+ # You must provide this unless your data is in the encoding
1382
+ # given by <tt>Encoding::default_external</tt>.
1383
+ # Parsing will use this to determine how to parse the data.
1384
+ # You may provide a second Encoding to
1385
+ # have the data transcoded as it is read. For example,
1386
+ # encoding: 'UTF-32BE:UTF-8'
1387
+ # would read +UTF-32BE+ data from the file
1388
+ # but transcode it to +UTF-8+ before parsing.
1389
+ def foreach(path, mode="r", **options, &block)
1390
+ return to_enum(__method__, path, mode, **options) unless block_given?
1391
+ open(path, mode, **options) do |csv|
1392
+ csv.each(&block)
1393
+ end
1394
+ end
1395
+
1396
+ #
1397
+ # :call-seq:
1398
+ # generate(csv_string, **options) {|csv| ... }
1399
+ # generate(**options) {|csv| ... }
1400
+ #
1401
+ # * Argument +csv_string+, if given, must be a \String object;
1402
+ # defaults to a new empty \String.
1403
+ # * Arguments +options+, if given, should be generating options.
1404
+ # See {Options for Generating}[#class-CSV-label-Options+for+Generating].
1405
+ #
1406
+ # ---
1407
+ #
1408
+ # Creates a new \CSV object via <tt>CSV.new(csv_string, **options)</tt>;
1409
+ # calls the block with the \CSV object, which the block may modify;
1410
+ # returns the \String generated from the \CSV object.
1411
+ #
1412
+ # Note that a passed \String *is* modified by this method.
1413
+ # Pass <tt>csv_string</tt>.dup if the \String must be preserved.
1414
+ #
1415
+ # This method has one additional option: <tt>:encoding</tt>,
1416
+ # which sets the base Encoding for the output if no no +str+ is specified.
1417
+ # CSV needs this hint if you plan to output non-ASCII compatible data.
1418
+ #
1419
+ # ---
1420
+ #
1421
+ # Add lines:
1422
+ # input_string = "foo,0\nbar,1\nbaz,2\n"
1423
+ # output_string = CSV.generate(input_string) do |csv|
1424
+ # csv << ['bat', 3]
1425
+ # csv << ['bam', 4]
1426
+ # end
1427
+ # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1428
+ # input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1429
+ # output_string.equal?(input_string) # => true # Same string, modified
1430
+ #
1431
+ # Add lines into new string, preserving old string:
1432
+ # input_string = "foo,0\nbar,1\nbaz,2\n"
1433
+ # output_string = CSV.generate(input_string.dup) do |csv|
1434
+ # csv << ['bat', 3]
1435
+ # csv << ['bam', 4]
1436
+ # end
1437
+ # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1438
+ # input_string # => "foo,0\nbar,1\nbaz,2\n"
1439
+ # output_string.equal?(input_string) # => false # Different strings
1440
+ #
1441
+ # Create lines from nothing:
1442
+ # output_string = CSV.generate do |csv|
1443
+ # csv << ['foo', 0]
1444
+ # csv << ['bar', 1]
1445
+ # csv << ['baz', 2]
1446
+ # end
1447
+ # output_string # => "foo,0\nbar,1\nbaz,2\n"
1448
+ #
1449
+ # ---
1450
+ #
1451
+ # Raises an exception if +csv_string+ is not a \String object:
1452
+ # # Raises TypeError (no implicit conversion of Integer into String)
1453
+ # CSV.generate(0)
1454
+ #
1455
+ def generate(str=nil, **options)
1456
+ encoding = options[:encoding]
1457
+ # add a default empty String, if none was given
1458
+ if str
1459
+ str = StringIO.new(str)
1460
+ str.seek(0, IO::SEEK_END)
1461
+ str.set_encoding(encoding) if encoding
1462
+ else
1463
+ str = +""
1464
+ str.force_encoding(encoding) if encoding
1465
+ end
1466
+ csv = new(str, **options) # wrap
1467
+ yield csv # yield for appending
1468
+ csv.string # return final String
1469
+ end
1470
+
1471
+ # :call-seq:
1472
+ # CSV.generate_line(ary)
1473
+ # CSV.generate_line(ary, **options)
1474
+ #
1475
+ # Returns the \String created by generating \CSV from +ary+
1476
+ # using the specified +options+.
1477
+ #
1478
+ # Argument +ary+ must be an \Array.
1479
+ #
1480
+ # Special options:
1481
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
1482
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
1483
+ # $INPUT_RECORD_SEPARATOR # => "\n"
1484
+ # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
1485
+ # Encoding for the output. This method will try to guess your Encoding from
1486
+ # the first non-+nil+ field in +row+, if possible, but you may need to use
1487
+ # this parameter as a backup plan.
1488
+ #
1489
+ # For other +options+,
1490
+ # see {Options for Generating}[#class-CSV-label-Options+for+Generating].
1491
+ #
1492
+ # ---
1493
+ #
1494
+ # Returns the \String generated from an \Array:
1495
+ # CSV.generate_line(['foo', '0']) # => "foo,0\n"
1496
+ #
1497
+ # ---
1498
+ #
1499
+ # Raises an exception if +ary+ is not an \Array:
1500
+ # # Raises NoMethodError (undefined method `find' for :foo:Symbol)
1501
+ # CSV.generate_line(:foo)
1502
+ #
1503
+ def generate_line(row, **options)
1504
+ options = {row_sep: InputRecordSeparator.value}.merge(options)
1505
+ str = +""
1506
+ if options[:encoding]
1507
+ str.force_encoding(options[:encoding])
1508
+ else
1509
+ fallback_encoding = nil
1510
+ output_encoding = nil
1511
+ row.each do |field|
1512
+ next unless field.is_a?(String)
1513
+ fallback_encoding ||= field.encoding
1514
+ next if field.ascii_only?
1515
+ output_encoding = field.encoding
1516
+ break
1517
+ end
1518
+ output_encoding ||= fallback_encoding
1519
+ if output_encoding
1520
+ str.force_encoding(output_encoding)
1521
+ end
1522
+ end
1523
+ (new(str, **options) << row).string
1524
+ end
1525
+
1526
+ # :call-seq:
1527
+ # CSV.generate_lines(rows)
1528
+ # CSV.generate_lines(rows, **options)
1529
+ #
1530
+ # Returns the \String created by generating \CSV from
1531
+ # using the specified +options+.
1532
+ #
1533
+ # Argument +rows+ must be an \Array of row. Row is \Array of \String or \CSV::Row.
1534
+ #
1535
+ # Special options:
1536
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"</tt> on Ruby 3.0 or later
1537
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
1538
+ # $INPUT_RECORD_SEPARATOR # => "\n"
1539
+ # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
1540
+ # Encoding for the output. This method will try to guess your Encoding from
1541
+ # the first non-+nil+ field in +row+, if possible, but you may need to use
1542
+ # this parameter as a backup plan.
1543
+ #
1544
+ # For other +options+,
1545
+ # see {Options for Generating}[#class-CSV-label-Options+for+Generating].
1546
+ #
1547
+ # ---
1548
+ #
1549
+ # Returns the \String generated from an
1550
+ # CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n"
1551
+ #
1552
+ # ---
1553
+ #
1554
+ # Raises an exception
1555
+ # # Raises NoMethodError (undefined method `each' for :foo:Symbol)
1556
+ # CSV.generate_lines(:foo)
1557
+ #
1558
+ def generate_lines(rows, **options)
1559
+ self.generate(**options) do |csv|
1560
+ rows.each do |row|
1561
+ csv << row
1562
+ end
1563
+ end
1564
+ end
1565
+
1566
+ #
1567
+ # :call-seq:
1568
+ # open(path_or_io, mode = "rb", **options ) -> new_csv
1569
+ # open(path_or_io, mode = "rb", **options ) { |csv| ... } -> object
1570
+ #
1571
+ # possible options elements:
1572
+ # keyword form:
1573
+ # :invalid => nil # raise error on invalid byte sequence (default)
1574
+ # :invalid => :replace # replace invalid byte sequence
1575
+ # :undef => :replace # replace undefined conversion
1576
+ # :replace => string # replacement string ("?" or "\uFFFD" if not specified)
1577
+ #
1578
+ # * Argument +path_or_io+, must be a file path or an \IO stream.
1579
+ # :include: ../doc/csv/arguments/io.rdoc
1580
+ # * Argument +mode+, if given, must be a \File mode.
1581
+ # See {Access Modes}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Access+Modes].
1582
+ # * Arguments <tt>**options</tt> must be keyword options.
1583
+ # See {Options for Generating}[#class-CSV-label-Options+for+Generating].
1584
+ # * This method optionally accepts an additional <tt>:encoding</tt> option
1585
+ # that you can use to specify the Encoding of the data read from +path+ or +io+.
1586
+ # You must provide this unless your data is in the encoding
1587
+ # given by <tt>Encoding::default_external</tt>.
1588
+ # Parsing will use this to determine how to parse the data.
1589
+ # You may provide a second Encoding to
1590
+ # have the data transcoded as it is read. For example,
1591
+ # encoding: 'UTF-32BE:UTF-8'
1592
+ # would read +UTF-32BE+ data from the file
1593
+ # but transcode it to +UTF-8+ before parsing.
1594
+ #
1595
+ # ---
1596
+ #
1597
+ # These examples assume prior execution of:
1598
+ # string = "foo,0\nbar,1\nbaz,2\n"
1599
+ # path = 't.csv'
1600
+ # File.write(path, string)
1601
+ #
1602
+ # string_io = StringIO.new
1603
+ # string_io << "foo,0\nbar,1\nbaz,2\n"
1604
+ #
1605
+ # ---
1606
+ #
1607
+ # With no block given, returns a new \CSV object.
1608
+ #
1609
+ # Create a \CSV object using a file path:
1610
+ # csv = CSV.open(path)
1611
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1612
+ #
1613
+ # Create a \CSV object using an open \File:
1614
+ # csv = CSV.open(File.open(path))
1615
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1616
+ #
1617
+ # Create a \CSV object using a \StringIO:
1618
+ # csv = CSV.open(string_io)
1619
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1620
+ # ---
1621
+ #
1622
+ # With a block given, calls the block with the created \CSV object;
1623
+ # returns the block's return value:
1624
+ #
1625
+ # Using a file path:
1626
+ # csv = CSV.open(path) {|csv| p csv}
1627
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1628
+ # Output:
1629
+ # #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1630
+ #
1631
+ # Using an open \File:
1632
+ # csv = CSV.open(File.open(path)) {|csv| p csv}
1633
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1634
+ # Output:
1635
+ # #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1636
+ #
1637
+ # Using a \StringIO:
1638
+ # csv = CSV.open(string_io) {|csv| p csv}
1639
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1640
+ # Output:
1641
+ # #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1642
+ # ---
1643
+ #
1644
+ # Raises an exception if the argument is not a \String object or \IO object:
1645
+ # # Raises TypeError (no implicit conversion of Symbol into String)
1646
+ # CSV.open(:foo)
1647
+ def open(filename_or_io, mode="r", **options)
1648
+ # wrap a File opened with the remaining +args+ with no newline
1649
+ # decorator
1650
+ file_opts = {}
1651
+ may_enable_bom_detection_automatically(filename_or_io,
1652
+ mode,
1653
+ options,
1654
+ file_opts)
1655
+ file_opts.merge!(options)
1656
+ unless file_opts.key?(:newline)
1657
+ file_opts[:universal_newline] ||= false
1658
+ end
1659
+ options.delete(:invalid)
1660
+ options.delete(:undef)
1661
+ options.delete(:replace)
1662
+ options.delete_if {|k, _| /newline\z/.match?(k)}
1663
+
1664
+ if filename_or_io.is_a?(StringIO)
1665
+ f = create_stringio(filename_or_io.string, mode, **file_opts)
1666
+ else
1667
+ begin
1668
+ f = File.open(filename_or_io, mode, **file_opts)
1669
+ rescue ArgumentError => e
1670
+ raise unless /needs binmode/.match?(e.message) and mode == "r"
1671
+ mode = "rb"
1672
+ file_opts = {encoding: Encoding.default_external}.merge(file_opts)
1673
+ retry
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ csv = new(f, **options)
1679
+ rescue Exception
1680
+ f.close
1681
+ raise
1682
+ end
1683
+
1684
+ # handle blocks like Ruby's open(), not like the CSV library
1685
+ if block_given?
1686
+ begin
1687
+ yield csv
1688
+ ensure
1689
+ csv.close
1690
+ end
1691
+ else
1692
+ csv
1693
+ end
1694
+ end
1695
+
1696
+ #
1697
+ # :call-seq:
1698
+ # parse(string) -> array_of_arrays
1699
+ # parse(io) -> array_of_arrays
1700
+ # parse(string, headers: ..., **options) -> csv_table
1701
+ # parse(io, headers: ..., **options) -> csv_table
1702
+ # parse(string, **options) {|row| ... }
1703
+ # parse(io, **options) {|row| ... }
1704
+ #
1705
+ # Parses +string+ or +io+ using the specified +options+.
1706
+ #
1707
+ # - Argument +string+ should be a \String object;
1708
+ # it will be put into a new StringIO object positioned at the beginning.
1709
+ # :include: ../doc/csv/arguments/io.rdoc
1710
+ # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1711
+ #
1712
+ # ====== Without Option +headers+
1713
+ #
1714
+ # Without {option +headers+}[#class-CSV-label-Option+headers] case.
1715
+ #
1716
+ # These examples assume prior execution of:
1717
+ # string = "foo,0\nbar,1\nbaz,2\n"
1718
+ # path = 't.csv'
1719
+ # File.write(path, string)
1720
+ #
1721
+ # ---
1722
+ #
1723
+ # With no block given, returns an \Array of Arrays formed from the source.
1724
+ #
1725
+ # Parse a \String:
1726
+ # a_of_a = CSV.parse(string)
1727
+ # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1728
+ #
1729
+ # Parse an open \File:
1730
+ # a_of_a = File.open(path) do |file|
1731
+ # CSV.parse(file)
1732
+ # end
1733
+ # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1734
+ #
1735
+ # ---
1736
+ #
1737
+ # With a block given, calls the block with each parsed row:
1738
+ #
1739
+ # Parse a \String:
1740
+ # CSV.parse(string) {|row| p row }
1741
+ #
1742
+ # Output:
1743
+ # ["foo", "0"]
1744
+ # ["bar", "1"]
1745
+ # ["baz", "2"]
1746
+ #
1747
+ # Parse an open \File:
1748
+ # File.open(path) do |file|
1749
+ # CSV.parse(file) {|row| p row }
1750
+ # end
1751
+ #
1752
+ # Output:
1753
+ # ["foo", "0"]
1754
+ # ["bar", "1"]
1755
+ # ["baz", "2"]
1756
+ #
1757
+ # ====== With Option +headers+
1758
+ #
1759
+ # With {option +headers+}[#class-CSV-label-Option+headers] case.
1760
+ #
1761
+ # These examples assume prior execution of:
1762
+ # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
1763
+ # path = 't.csv'
1764
+ # File.write(path, string)
1765
+ #
1766
+ # ---
1767
+ #
1768
+ # With no block given, returns a CSV::Table object formed from the source.
1769
+ #
1770
+ # Parse a \String:
1771
+ # csv_table = CSV.parse(string, headers: ['Name', 'Count'])
1772
+ # csv_table # => #<CSV::Table mode:col_or_row row_count:5>
1773
+ #
1774
+ # Parse an open \File:
1775
+ # csv_table = File.open(path) do |file|
1776
+ # CSV.parse(file, headers: ['Name', 'Count'])
1777
+ # end
1778
+ # csv_table # => #<CSV::Table mode:col_or_row row_count:4>
1779
+ #
1780
+ # ---
1781
+ #
1782
+ # With a block given, calls the block with each parsed row,
1783
+ # which has been formed into a CSV::Row object:
1784
+ #
1785
+ # Parse a \String:
1786
+ # CSV.parse(string, headers: ['Name', 'Count']) {|row| p row }
1787
+ #
1788
+ # Output:
1789
+ # # <CSV::Row "Name":"foo" "Count":"0">
1790
+ # # <CSV::Row "Name":"bar" "Count":"1">
1791
+ # # <CSV::Row "Name":"baz" "Count":"2">
1792
+ #
1793
+ # Parse an open \File:
1794
+ # File.open(path) do |file|
1795
+ # CSV.parse(file, headers: ['Name', 'Count']) {|row| p row }
1796
+ # end
1797
+ #
1798
+ # Output:
1799
+ # # <CSV::Row "Name":"foo" "Count":"0">
1800
+ # # <CSV::Row "Name":"bar" "Count":"1">
1801
+ # # <CSV::Row "Name":"baz" "Count":"2">
1802
+ #
1803
+ # ---
1804
+ #
1805
+ # Raises an exception if the argument is not a \String object or \IO object:
1806
+ # # Raises NoMethodError (undefined method `close' for :foo:Symbol)
1807
+ # CSV.parse(:foo)
1808
+ #
1809
+ # ---
1810
+ #
1811
+ # Please make sure if your text contains \BOM or not. CSV.parse will not remove
1812
+ # \BOM automatically. You might want to remove \BOM before calling CSV.parse :
1813
+ # # remove BOM on calling File.open
1814
+ # File.open(path, encoding: 'bom|utf-8') do |file|
1815
+ # CSV.parse(file, headers: true) do |row|
1816
+ # # you can get value by column name because BOM is removed
1817
+ # p row['Name']
1818
+ # end
1819
+ # end
1820
+ #
1821
+ # Output:
1822
+ # # "foo"
1823
+ # # "bar"
1824
+ # # "baz"
1825
+ def parse(str, **options, &block)
1826
+ csv = new(str, **options)
1827
+
1828
+ return csv.each(&block) if block_given?
1829
+
1830
+ # slurp contents, if no block is given
1831
+ begin
1832
+ csv.read
1833
+ ensure
1834
+ csv.close
1835
+ end
1836
+ end
1837
+
1838
+ # :call-seq:
1839
+ # CSV.parse_line(string) -> new_array or nil
1840
+ # CSV.parse_line(io) -> new_array or nil
1841
+ # CSV.parse_line(string, **options) -> new_array or nil
1842
+ # CSV.parse_line(io, **options) -> new_array or nil
1843
+ # CSV.parse_line(string, headers: true, **options) -> csv_row or nil
1844
+ # CSV.parse_line(io, headers: true, **options) -> csv_row or nil
1845
+ #
1846
+ # Returns the data created by parsing the first line of +string+ or +io+
1847
+ # using the specified +options+.
1848
+ #
1849
+ # - Argument +string+ should be a \String object;
1850
+ # it will be put into a new StringIO object positioned at the beginning.
1851
+ # :include: ../doc/csv/arguments/io.rdoc
1852
+ # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1853
+ #
1854
+ # ====== Without Option +headers+
1855
+ #
1856
+ # Without option +headers+, returns the first row as a new \Array.
1857
+ #
1858
+ # These examples assume prior execution of:
1859
+ # string = "foo,0\nbar,1\nbaz,2\n"
1860
+ # path = 't.csv'
1861
+ # File.write(path, string)
1862
+ #
1863
+ # Parse the first line from a \String object:
1864
+ # CSV.parse_line(string) # => ["foo", "0"]
1865
+ #
1866
+ # Parse the first line from a File object:
1867
+ # File.open(path) do |file|
1868
+ # CSV.parse_line(file) # => ["foo", "0"]
1869
+ # end # => ["foo", "0"]
1870
+ #
1871
+ # Returns +nil+ if the argument is an empty \String:
1872
+ # CSV.parse_line('') # => nil
1873
+ #
1874
+ # ====== With Option +headers+
1875
+ #
1876
+ # With {option +headers+}[#class-CSV-label-Option+headers],
1877
+ # returns the first row as a CSV::Row object.
1878
+ #
1879
+ # These examples assume prior execution of:
1880
+ # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
1881
+ # path = 't.csv'
1882
+ # File.write(path, string)
1883
+ #
1884
+ # Parse the first line from a \String object:
1885
+ # CSV.parse_line(string, headers: true) # => #<CSV::Row "Name":"foo" "Count":"0">
1886
+ #
1887
+ # Parse the first line from a File object:
1888
+ # File.open(path) do |file|
1889
+ # CSV.parse_line(file, headers: true)
1890
+ # end # => #<CSV::Row "Name":"foo" "Count":"0">
1891
+ #
1892
+ # ---
1893
+ #
1894
+ # Raises an exception if the argument is +nil+:
1895
+ # # Raises ArgumentError (Cannot parse nil as CSV):
1896
+ # CSV.parse_line(nil)
1897
+ #
1898
+ def parse_line(line, **options)
1899
+ new(line, **options).each.first
1900
+ end
1901
+
1902
+ #
1903
+ # :call-seq:
1904
+ # read(source, **options) -> array_of_arrays
1905
+ # read(source, headers: true, **options) -> csv_table
1906
+ #
1907
+ # Opens the given +source+ with the given +options+ (see CSV.open),
1908
+ # reads the source (see CSV#read), and returns the result,
1909
+ # which will be either an \Array of Arrays or a CSV::Table.
1910
+ #
1911
+ # Without headers:
1912
+ # string = "foo,0\nbar,1\nbaz,2\n"
1913
+ # path = 't.csv'
1914
+ # File.write(path, string)
1915
+ # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1916
+ #
1917
+ # With headers:
1918
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1919
+ # path = 't.csv'
1920
+ # File.write(path, string)
1921
+ # CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
1922
+ def read(path, **options)
1923
+ open(path, **options) { |csv| csv.read }
1924
+ end
1925
+
1926
+ # :call-seq:
1927
+ # CSV.readlines(source, **options)
1928
+ #
1929
+ # Alias for CSV.read.
1930
+ def readlines(path, **options)
1931
+ read(path, **options)
1932
+ end
1933
+
1934
+ # :call-seq:
1935
+ # CSV.table(source, **options)
1936
+ #
1937
+ # Calls CSV.read with +source+, +options+, and certain default options:
1938
+ # - +headers+: +true+
1939
+ # - +converters+: +:numeric+
1940
+ # - +header_converters+: +:symbol+
1941
+ #
1942
+ # Returns a CSV::Table object.
1943
+ #
1944
+ # Example:
1945
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1946
+ # path = 't.csv'
1947
+ # File.write(path, string)
1948
+ # CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:4>
1949
+ def table(path, **options)
1950
+ default_options = {
1951
+ headers: true,
1952
+ converters: :numeric,
1953
+ header_converters: :symbol,
1954
+ }
1955
+ options = default_options.merge(options)
1956
+ read(path, **options)
1957
+ end
1958
+
1959
+ ON_WINDOWS = /mingw|mswin/.match?(RUBY_PLATFORM)
1960
+ private_constant :ON_WINDOWS
1961
+
1962
+ private
1963
+ def may_enable_bom_detection_automatically(filename_or_io,
1964
+ mode,
1965
+ options,
1966
+ file_opts)
1967
+ if filename_or_io.is_a?(StringIO)
1968
+ # Support to StringIO was dropped for Ruby 2.6 and earlier without BOM support:
1969
+ # https://github.com/ruby/stringio/pull/47
1970
+ return if RUBY_VERSION < "2.7"
1971
+ else
1972
+ # "bom|utf-8" may be buggy on Windows:
1973
+ # https://bugs.ruby-lang.org/issues/20526
1974
+ return if ON_WINDOWS
1975
+ end
1976
+ return unless Encoding.default_external == Encoding::UTF_8
1977
+ return if options.key?(:encoding)
1978
+ return if options.key?(:external_encoding)
1979
+ return if mode.include?(":")
1980
+ file_opts[:encoding] = "bom|utf-8"
1981
+ end
1982
+
1983
+ if RUBY_VERSION < "2.7"
1984
+ def create_stringio(str, mode, opts)
1985
+ opts.delete_if {|k, _| k == :universal_newline or DEFAULT_OPTIONS.key?(k)}
1986
+ raise ArgumentError, "Unsupported options parsing StringIO: #{opts.keys}" unless opts.empty?
1987
+ StringIO.new(str, mode)
1988
+ end
1989
+ else
1990
+ def create_stringio(str, mode, opts)
1991
+ StringIO.new(str, mode, **opts)
1992
+ end
1993
+ end
1994
+ end
1995
+
1996
+ # :call-seq:
1997
+ # CSV.new(string)
1998
+ # CSV.new(io)
1999
+ # CSV.new(string, **options)
2000
+ # CSV.new(io, **options)
2001
+ #
2002
+ # Returns the new \CSV object created using +string+ or +io+
2003
+ # and the specified +options+.
2004
+ #
2005
+ # - Argument +string+ should be a \String object;
2006
+ # it will be put into a new StringIO object positioned at the beginning.
2007
+ # :include: ../doc/csv/arguments/io.rdoc
2008
+ # - Argument +options+: See:
2009
+ # * {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
2010
+ # * {Options for Generating}[#class-CSV-label-Options+for+Generating]
2011
+ # For performance reasons, the options cannot be overridden
2012
+ # in a \CSV object, so those specified here will endure.
2013
+ #
2014
+ # In addition to the \CSV instance methods, several \IO methods are delegated.
2015
+ # See {Delegated Methods}[#class-CSV-label-Delegated+Methods].
2016
+ #
2017
+ # ---
2018
+ #
2019
+ # Create a \CSV object from a \String object:
2020
+ # csv = CSV.new('foo,0')
2021
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
2022
+ #
2023
+ # Create a \CSV object from a \File object:
2024
+ # File.write('t.csv', 'foo,0')
2025
+ # csv = CSV.new(File.open('t.csv'))
2026
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
2027
+ #
2028
+ # ---
2029
+ #
2030
+ # Raises an exception if the argument is +nil+:
2031
+ # # Raises ArgumentError (Cannot parse nil as CSV):
2032
+ # CSV.new(nil)
2033
+ #
2034
+ def initialize(data,
2035
+ col_sep: ",",
2036
+ row_sep: :auto,
2037
+ quote_char: '"',
2038
+ field_size_limit: nil,
2039
+ max_field_size: nil,
2040
+ converters: nil,
2041
+ unconverted_fields: nil,
2042
+ headers: false,
2043
+ return_headers: false,
2044
+ write_headers: nil,
2045
+ header_converters: nil,
2046
+ skip_blanks: false,
2047
+ force_quotes: false,
2048
+ skip_lines: nil,
2049
+ liberal_parsing: false,
2050
+ internal_encoding: nil,
2051
+ external_encoding: nil,
2052
+ encoding: nil,
2053
+ nil_value: nil,
2054
+ empty_value: "",
2055
+ strip: false,
2056
+ quote_empty: true,
2057
+ write_converters: nil,
2058
+ write_nil_value: nil,
2059
+ write_empty_value: "")
2060
+ raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
2061
+
2062
+ if data.is_a?(String)
2063
+ if encoding
2064
+ if encoding.is_a?(String)
2065
+ data_external_encoding, data_internal_encoding = encoding.split(":", 2)
2066
+ if data_internal_encoding
2067
+ data = data.encode(data_internal_encoding, data_external_encoding)
2068
+ else
2069
+ data = data.dup.force_encoding(data_external_encoding)
2070
+ end
2071
+ else
2072
+ data = data.dup.force_encoding(encoding)
2073
+ end
2074
+ end
2075
+ @io = StringIO.new(data)
2076
+ else
2077
+ @io = data
2078
+ end
2079
+ @encoding = determine_encoding(encoding, internal_encoding)
2080
+
2081
+ @base_fields_converter_options = {
2082
+ nil_value: nil_value,
2083
+ empty_value: empty_value,
2084
+ }
2085
+ @write_fields_converter_options = {
2086
+ nil_value: write_nil_value,
2087
+ empty_value: write_empty_value,
2088
+ }
2089
+ @initial_converters = converters
2090
+ @initial_header_converters = header_converters
2091
+ @initial_write_converters = write_converters
2092
+
2093
+ if max_field_size.nil? and field_size_limit
2094
+ max_field_size = field_size_limit - 1
2095
+ end
2096
+ @parser_options = {
2097
+ column_separator: col_sep,
2098
+ row_separator: row_sep,
2099
+ quote_character: quote_char,
2100
+ max_field_size: max_field_size,
2101
+ unconverted_fields: unconverted_fields,
2102
+ headers: headers,
2103
+ return_headers: return_headers,
2104
+ skip_blanks: skip_blanks,
2105
+ skip_lines: skip_lines,
2106
+ liberal_parsing: liberal_parsing,
2107
+ encoding: @encoding,
2108
+ nil_value: nil_value,
2109
+ empty_value: empty_value,
2110
+ strip: strip,
2111
+ }
2112
+ @parser = nil
2113
+ @parser_enumerator = nil
2114
+ @eof_error = nil
2115
+
2116
+ @writer_options = {
2117
+ encoding: @encoding,
2118
+ force_encoding: (not encoding.nil?),
2119
+ force_quotes: force_quotes,
2120
+ headers: headers,
2121
+ write_headers: write_headers,
2122
+ column_separator: col_sep,
2123
+ row_separator: row_sep,
2124
+ quote_character: quote_char,
2125
+ quote_empty: quote_empty,
2126
+ }
2127
+
2128
+ @writer = nil
2129
+ writer if @writer_options[:write_headers]
2130
+ end
2131
+
2132
+ class TSV < CSV
2133
+ def initialize(data, **options)
2134
+ super(data, **({col_sep: "\t"}.merge(options)))
2135
+ end
2136
+ end
2137
+
2138
+ # :call-seq:
2139
+ # csv.col_sep -> string
2140
+ #
2141
+ # Returns the encoded column separator; used for parsing and writing;
2142
+ # see {Option +col_sep+}[#class-CSV-label-Option+col_sep]:
2143
+ # CSV.new('').col_sep # => ","
2144
+ def col_sep
2145
+ parser.column_separator
2146
+ end
2147
+
2148
+ # :call-seq:
2149
+ # csv.row_sep -> string
2150
+ #
2151
+ # Returns the encoded row separator; used for parsing and writing;
2152
+ # see {Option +row_sep+}[#class-CSV-label-Option+row_sep]:
2153
+ # CSV.new('').row_sep # => "\n"
2154
+ def row_sep
2155
+ parser.row_separator
2156
+ end
2157
+
2158
+ # :call-seq:
2159
+ # csv.quote_char -> character
2160
+ #
2161
+ # Returns the encoded quote character; used for parsing and writing;
2162
+ # see {Option +quote_char+}[#class-CSV-label-Option+quote_char]:
2163
+ # CSV.new('').quote_char # => "\""
2164
+ def quote_char
2165
+ parser.quote_character
2166
+ end
2167
+
2168
+ # :call-seq:
2169
+ # csv.field_size_limit -> integer or nil
2170
+ #
2171
+ # Returns the limit for field size; used for parsing;
2172
+ # see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]:
2173
+ # CSV.new('').field_size_limit # => nil
2174
+ #
2175
+ # Deprecated since 3.2.3. Use +max_field_size+ instead.
2176
+ def field_size_limit
2177
+ parser.field_size_limit
2178
+ end
2179
+
2180
+ # :call-seq:
2181
+ # csv.max_field_size -> integer or nil
2182
+ #
2183
+ # Returns the limit for field size; used for parsing;
2184
+ # see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]:
2185
+ # CSV.new('').max_field_size # => nil
2186
+ #
2187
+ # Since 3.2.3.
2188
+ def max_field_size
2189
+ parser.max_field_size
2190
+ end
2191
+
2192
+ # :call-seq:
2193
+ # csv.skip_lines -> regexp or nil
2194
+ #
2195
+ # Returns the \Regexp used to identify comment lines; used for parsing;
2196
+ # see {Option +skip_lines+}[#class-CSV-label-Option+skip_lines]:
2197
+ # CSV.new('').skip_lines # => nil
2198
+ def skip_lines
2199
+ parser.skip_lines
2200
+ end
2201
+
2202
+ # :call-seq:
2203
+ # csv.converters -> array
2204
+ #
2205
+ # Returns an \Array containing field converters;
2206
+ # see {Field Converters}[#class-CSV-label-Field+Converters]:
2207
+ # csv = CSV.new('')
2208
+ # csv.converters # => []
2209
+ # csv.convert(:integer)
2210
+ # csv.converters # => [:integer]
2211
+ # csv.convert(proc {|x| x.to_s })
2212
+ # csv.converters
2213
+ #
2214
+ # Notes that you need to call
2215
+ # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
2216
+ # this method.
2217
+ def converters
2218
+ parser_fields_converter.map do |converter|
2219
+ name = Converters.rassoc(converter)
2220
+ name ? name.first : converter
2221
+ end
2222
+ end
2223
+
2224
+ # :call-seq:
2225
+ # csv.unconverted_fields? -> object
2226
+ #
2227
+ # Returns the value that determines whether unconverted fields are to be
2228
+ # available; used for parsing;
2229
+ # see {Option +unconverted_fields+}[#class-CSV-label-Option+unconverted_fields]:
2230
+ # CSV.new('').unconverted_fields? # => nil
2231
+ def unconverted_fields?
2232
+ parser.unconverted_fields?
2233
+ end
2234
+
2235
+ # :call-seq:
2236
+ # csv.headers -> object
2237
+ #
2238
+ # Returns the value that determines whether headers are used; used for parsing;
2239
+ # see {Option +headers+}[#class-CSV-label-Option+headers]:
2240
+ # CSV.new('').headers # => nil
2241
+ def headers
2242
+ if @writer
2243
+ @writer.headers
2244
+ else
2245
+ parsed_headers = parser.headers
2246
+ return parsed_headers if parsed_headers
2247
+ raw_headers = @parser_options[:headers]
2248
+ raw_headers = nil if raw_headers == false
2249
+ raw_headers
2250
+ end
2251
+ end
2252
+
2253
+ # :call-seq:
2254
+ # csv.return_headers? -> true or false
2255
+ #
2256
+ # Returns the value that determines whether headers are to be returned; used for parsing;
2257
+ # see {Option +return_headers+}[#class-CSV-label-Option+return_headers]:
2258
+ # CSV.new('').return_headers? # => false
2259
+ def return_headers?
2260
+ parser.return_headers?
2261
+ end
2262
+
2263
+ # :call-seq:
2264
+ # csv.write_headers? -> true or false
2265
+ #
2266
+ # Returns the value that determines whether headers are to be written; used for generating;
2267
+ # see {Option +write_headers+}[#class-CSV-label-Option+write_headers]:
2268
+ # CSV.new('').write_headers? # => nil
2269
+ def write_headers?
2270
+ @writer_options[:write_headers]
2271
+ end
2272
+
2273
+ # :call-seq:
2274
+ # csv.header_converters -> array
2275
+ #
2276
+ # Returns an \Array containing header converters; used for parsing;
2277
+ # see {Header Converters}[#class-CSV-label-Header+Converters]:
2278
+ # CSV.new('').header_converters # => []
2279
+ #
2280
+ # Notes that you need to call
2281
+ # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
2282
+ # to use this method.
2283
+ def header_converters
2284
+ header_fields_converter.map do |converter|
2285
+ name = HeaderConverters.rassoc(converter)
2286
+ name ? name.first : converter
2287
+ end
2288
+ end
2289
+
2290
+ # :call-seq:
2291
+ # csv.skip_blanks? -> true or false
2292
+ #
2293
+ # Returns the value that determines whether blank lines are to be ignored; used for parsing;
2294
+ # see {Option +skip_blanks+}[#class-CSV-label-Option+skip_blanks]:
2295
+ # CSV.new('').skip_blanks? # => false
2296
+ def skip_blanks?
2297
+ parser.skip_blanks?
2298
+ end
2299
+
2300
+ # :call-seq:
2301
+ # csv.force_quotes? -> true or false
2302
+ #
2303
+ # Returns the value that determines whether all output fields are to be quoted;
2304
+ # used for generating;
2305
+ # see {Option +force_quotes+}[#class-CSV-label-Option+force_quotes]:
2306
+ # CSV.new('').force_quotes? # => false
2307
+ def force_quotes?
2308
+ @writer_options[:force_quotes]
2309
+ end
2310
+
2311
+ # :call-seq:
2312
+ # csv.liberal_parsing? -> true or false
2313
+ #
2314
+ # Returns the value that determines whether illegal input is to be handled; used for parsing;
2315
+ # see {Option +liberal_parsing+}[#class-CSV-label-Option+liberal_parsing]:
2316
+ # CSV.new('').liberal_parsing? # => false
2317
+ def liberal_parsing?
2318
+ parser.liberal_parsing?
2319
+ end
2320
+
2321
+ # :call-seq:
2322
+ # csv.encoding -> encoding
2323
+ #
2324
+ # Returns the encoding used for parsing and generating;
2325
+ # see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
2326
+ # CSV.new('').encoding # => #<Encoding:UTF-8>
2327
+ attr_reader :encoding
2328
+
2329
+ # :call-seq:
2330
+ # csv.line_no -> integer
2331
+ #
2332
+ # Returns the count of the rows parsed or generated.
2333
+ #
2334
+ # Parsing:
2335
+ # string = "foo,0\nbar,1\nbaz,2\n"
2336
+ # path = 't.csv'
2337
+ # File.write(path, string)
2338
+ # CSV.open(path) do |csv|
2339
+ # csv.each do |row|
2340
+ # p [csv.lineno, row]
2341
+ # end
2342
+ # end
2343
+ # Output:
2344
+ # [1, ["foo", "0"]]
2345
+ # [2, ["bar", "1"]]
2346
+ # [3, ["baz", "2"]]
2347
+ #
2348
+ # Generating:
2349
+ # CSV.generate do |csv|
2350
+ # p csv.lineno; csv << ['foo', 0]
2351
+ # p csv.lineno; csv << ['bar', 1]
2352
+ # p csv.lineno; csv << ['baz', 2]
2353
+ # end
2354
+ # Output:
2355
+ # 0
2356
+ # 1
2357
+ # 2
2358
+ def lineno
2359
+ if @writer
2360
+ @writer.lineno
2361
+ else
2362
+ parser.lineno
2363
+ end
2364
+ end
2365
+
2366
+ # :call-seq:
2367
+ # csv.line -> array
2368
+ #
2369
+ # Returns the line most recently read:
2370
+ # string = "foo,0\nbar,1\nbaz,2\n"
2371
+ # path = 't.csv'
2372
+ # File.write(path, string)
2373
+ # CSV.open(path) do |csv|
2374
+ # csv.each do |row|
2375
+ # p [csv.lineno, csv.line]
2376
+ # end
2377
+ # end
2378
+ # Output:
2379
+ # [1, "foo,0\n"]
2380
+ # [2, "bar,1\n"]
2381
+ # [3, "baz,2\n"]
2382
+ def line
2383
+ parser.line
2384
+ end
2385
+
2386
+ ### IO and StringIO Delegation ###
2387
+
2388
+ extend Forwardable
2389
+ def_delegators :@io, :binmode, :close, :close_read, :close_write,
2390
+ :closed?, :external_encoding, :fcntl,
2391
+ :fileno, :flush, :fsync, :internal_encoding,
2392
+ :isatty, :pid, :pos, :pos=, :reopen,
2393
+ :seek, :string, :sync, :sync=, :tell,
2394
+ :truncate, :tty?
2395
+
2396
+ def binmode?
2397
+ if @io.respond_to?(:binmode?)
2398
+ @io.binmode?
2399
+ else
2400
+ false
2401
+ end
2402
+ end
2403
+
2404
+ def flock(*args)
2405
+ raise NotImplementedError unless @io.respond_to?(:flock)
2406
+ @io.flock(*args)
2407
+ end
2408
+
2409
+ def ioctl(*args)
2410
+ raise NotImplementedError unless @io.respond_to?(:ioctl)
2411
+ @io.ioctl(*args)
2412
+ end
2413
+
2414
+ def path
2415
+ @io.path if @io.respond_to?(:path)
2416
+ end
2417
+
2418
+ def stat(*args)
2419
+ raise NotImplementedError unless @io.respond_to?(:stat)
2420
+ @io.stat(*args)
2421
+ end
2422
+
2423
+ def to_i
2424
+ raise NotImplementedError unless @io.respond_to?(:to_i)
2425
+ @io.to_i
2426
+ end
2427
+
2428
+ def to_io
2429
+ @io.respond_to?(:to_io) ? @io.to_io : @io
2430
+ end
2431
+
2432
+ def eof?
2433
+ return false if @eof_error
2434
+ begin
2435
+ parser_enumerator.peek
2436
+ false
2437
+ rescue MalformedCSVError => error
2438
+ @eof_error = error
2439
+ false
2440
+ rescue StopIteration
2441
+ true
2442
+ end
2443
+ end
2444
+ alias_method :eof, :eof?
2445
+
2446
+ # Rewinds the underlying IO object and resets CSV's lineno() counter.
2447
+ def rewind
2448
+ @parser = nil
2449
+ @parser_enumerator = nil
2450
+ @eof_error = nil
2451
+ @writer.rewind if @writer
2452
+ @io.rewind
2453
+ end
2454
+
2455
+ ### End Delegation ###
2456
+
2457
+ # :call-seq:
2458
+ # csv << row -> self
2459
+ #
2460
+ # Appends a row to +self+.
2461
+ #
2462
+ # - Argument +row+ must be an \Array object or a CSV::Row object.
2463
+ # - The output stream must be open for writing.
2464
+ #
2465
+ # ---
2466
+ #
2467
+ # Append Arrays:
2468
+ # CSV.generate do |csv|
2469
+ # csv << ['foo', 0]
2470
+ # csv << ['bar', 1]
2471
+ # csv << ['baz', 2]
2472
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2473
+ #
2474
+ # Append CSV::Rows:
2475
+ # headers = []
2476
+ # CSV.generate do |csv|
2477
+ # csv << CSV::Row.new(headers, ['foo', 0])
2478
+ # csv << CSV::Row.new(headers, ['bar', 1])
2479
+ # csv << CSV::Row.new(headers, ['baz', 2])
2480
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2481
+ #
2482
+ # Headers in CSV::Row objects are not appended:
2483
+ # headers = ['Name', 'Count']
2484
+ # CSV.generate do |csv|
2485
+ # csv << CSV::Row.new(headers, ['foo', 0])
2486
+ # csv << CSV::Row.new(headers, ['bar', 1])
2487
+ # csv << CSV::Row.new(headers, ['baz', 2])
2488
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2489
+ #
2490
+ # ---
2491
+ #
2492
+ # Raises an exception if +row+ is not an \Array or \CSV::Row:
2493
+ # CSV.generate do |csv|
2494
+ # # Raises NoMethodError (undefined method `collect' for :foo:Symbol)
2495
+ # csv << :foo
2496
+ # end
2497
+ #
2498
+ # Raises an exception if the output stream is not opened for writing:
2499
+ # path = 't.csv'
2500
+ # File.write(path, '')
2501
+ # File.open(path) do |file|
2502
+ # CSV.open(file) do |csv|
2503
+ # # Raises IOError (not opened for writing)
2504
+ # csv << ['foo', 0]
2505
+ # end
2506
+ # end
2507
+ def <<(row)
2508
+ writer << row
2509
+ self
2510
+ end
2511
+ alias_method :add_row, :<<
2512
+ alias_method :puts, :<<
2513
+
2514
+ # :call-seq:
2515
+ # convert(converter_name) -> array_of_procs
2516
+ # convert {|field, field_info| ... } -> array_of_procs
2517
+ #
2518
+ # - With no block, installs a field converter (a \Proc).
2519
+ # - With a block, defines and installs a custom field converter.
2520
+ # - Returns the \Array of installed field converters.
2521
+ #
2522
+ # - Argument +converter_name+, if given, should be the name
2523
+ # of an existing field converter.
2524
+ #
2525
+ # See {Field Converters}[#class-CSV-label-Field+Converters].
2526
+ # ---
2527
+ #
2528
+ # With no block, installs a field converter:
2529
+ # csv = CSV.new('')
2530
+ # csv.convert(:integer)
2531
+ # csv.convert(:float)
2532
+ # csv.convert(:date)
2533
+ # csv.converters # => [:integer, :float, :date]
2534
+ #
2535
+ # ---
2536
+ #
2537
+ # The block, if given, is called for each field:
2538
+ # - Argument +field+ is the field value.
2539
+ # - Argument +field_info+ is a CSV::FieldInfo object
2540
+ # containing details about the field.
2541
+ #
2542
+ # The examples here assume the prior execution of:
2543
+ # string = "foo,0\nbar,1\nbaz,2\n"
2544
+ # path = 't.csv'
2545
+ # File.write(path, string)
2546
+ #
2547
+ # Example giving a block:
2548
+ # csv = CSV.open(path)
2549
+ # csv.convert {|field, field_info| p [field, field_info]; field.upcase }
2550
+ # csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]
2551
+ #
2552
+ # Output:
2553
+ # ["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
2554
+ # ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
2555
+ # ["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
2556
+ # ["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
2557
+ # ["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
2558
+ # ["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
2559
+ #
2560
+ # The block need not return a \String object:
2561
+ # csv = CSV.open(path)
2562
+ # csv.convert {|field, field_info| field.to_sym }
2563
+ # csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]
2564
+ #
2565
+ # If +converter_name+ is given, the block is not called:
2566
+ # csv = CSV.open(path)
2567
+ # csv.convert(:integer) {|field, field_info| fail 'Cannot happen' }
2568
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
2569
+ #
2570
+ # ---
2571
+ #
2572
+ # Raises a parse-time exception if +converter_name+ is not the name of a built-in
2573
+ # field converter:
2574
+ # csv = CSV.open(path)
2575
+ # csv.convert(:nosuch) => [nil]
2576
+ # # Raises NoMethodError (undefined method `arity' for nil:NilClass)
2577
+ # csv.read
2578
+ def convert(name = nil, &converter)
2579
+ parser_fields_converter.add_converter(name, &converter)
2580
+ end
2581
+
2582
+ # :call-seq:
2583
+ # header_convert(converter_name) -> array_of_procs
2584
+ # header_convert {|header, field_info| ... } -> array_of_procs
2585
+ #
2586
+ # - With no block, installs a header converter (a \Proc).
2587
+ # - With a block, defines and installs a custom header converter.
2588
+ # - Returns the \Array of installed header converters.
2589
+ #
2590
+ # - Argument +converter_name+, if given, should be the name
2591
+ # of an existing header converter.
2592
+ #
2593
+ # See {Header Converters}[#class-CSV-label-Header+Converters].
2594
+ # ---
2595
+ #
2596
+ # With no block, installs a header converter:
2597
+ # csv = CSV.new('')
2598
+ # csv.header_convert(:symbol)
2599
+ # csv.header_convert(:downcase)
2600
+ # csv.header_converters # => [:symbol, :downcase]
2601
+ #
2602
+ # ---
2603
+ #
2604
+ # The block, if given, is called for each header:
2605
+ # - Argument +header+ is the header value.
2606
+ # - Argument +field_info+ is a CSV::FieldInfo object
2607
+ # containing details about the header.
2608
+ #
2609
+ # The examples here assume the prior execution of:
2610
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2611
+ # path = 't.csv'
2612
+ # File.write(path, string)
2613
+ #
2614
+ # Example giving a block:
2615
+ # csv = CSV.open(path, headers: true)
2616
+ # csv.header_convert {|header, field_info| p [header, field_info]; header.upcase }
2617
+ # table = csv.read
2618
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
2619
+ # table.headers # => ["NAME", "VALUE"]
2620
+ #
2621
+ # Output:
2622
+ # ["Name", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
2623
+ # ["Value", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
2624
+
2625
+ # The block need not return a \String object:
2626
+ # csv = CSV.open(path, headers: true)
2627
+ # csv.header_convert {|header, field_info| header.to_sym }
2628
+ # table = csv.read
2629
+ # table.headers # => [:Name, :Value]
2630
+ #
2631
+ # If +converter_name+ is given, the block is not called:
2632
+ # csv = CSV.open(path, headers: true)
2633
+ # csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' }
2634
+ # table = csv.read
2635
+ # table.headers # => ["name", "value"]
2636
+ # ---
2637
+ #
2638
+ # Raises a parse-time exception if +converter_name+ is not the name of a built-in
2639
+ # field converter:
2640
+ # csv = CSV.open(path, headers: true)
2641
+ # csv.header_convert(:nosuch)
2642
+ # # Raises NoMethodError (undefined method `arity' for nil:NilClass)
2643
+ # csv.read
2644
+ def header_convert(name = nil, &converter)
2645
+ header_fields_converter.add_converter(name, &converter)
2646
+ end
2647
+
2648
+ include Enumerable
2649
+
2650
+ # :call-seq:
2651
+ # csv.each -> enumerator
2652
+ # csv.each {|row| ...}
2653
+ #
2654
+ # Calls the block with each successive row.
2655
+ # The data source must be opened for reading.
2656
+ #
2657
+ # Without headers:
2658
+ # string = "foo,0\nbar,1\nbaz,2\n"
2659
+ # csv = CSV.new(string)
2660
+ # csv.each do |row|
2661
+ # p row
2662
+ # end
2663
+ # Output:
2664
+ # ["foo", "0"]
2665
+ # ["bar", "1"]
2666
+ # ["baz", "2"]
2667
+ #
2668
+ # With headers:
2669
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2670
+ # csv = CSV.new(string, headers: true)
2671
+ # csv.each do |row|
2672
+ # p row
2673
+ # end
2674
+ # Output:
2675
+ # <CSV::Row "Name":"foo" "Value":"0">
2676
+ # <CSV::Row "Name":"bar" "Value":"1">
2677
+ # <CSV::Row "Name":"baz" "Value":"2">
2678
+ #
2679
+ # ---
2680
+ #
2681
+ # Raises an exception if the source is not opened for reading:
2682
+ # string = "foo,0\nbar,1\nbaz,2\n"
2683
+ # csv = CSV.new(string)
2684
+ # csv.close
2685
+ # # Raises IOError (not opened for reading)
2686
+ # csv.each do |row|
2687
+ # p row
2688
+ # end
2689
+ def each(&block)
2690
+ return to_enum(__method__) unless block_given?
2691
+ begin
2692
+ while true
2693
+ yield(parser_enumerator.next)
2694
+ end
2695
+ rescue StopIteration
2696
+ end
2697
+ end
2698
+
2699
+ # :call-seq:
2700
+ # csv.read -> array or csv_table
2701
+ #
2702
+ # Forms the remaining rows from +self+ into:
2703
+ # - A CSV::Table object, if headers are in use.
2704
+ # - An \Array of Arrays, otherwise.
2705
+ #
2706
+ # The data source must be opened for reading.
2707
+ #
2708
+ # Without headers:
2709
+ # string = "foo,0\nbar,1\nbaz,2\n"
2710
+ # path = 't.csv'
2711
+ # File.write(path, string)
2712
+ # csv = CSV.open(path)
2713
+ # csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
2714
+ #
2715
+ # With headers:
2716
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2717
+ # path = 't.csv'
2718
+ # File.write(path, string)
2719
+ # csv = CSV.open(path, headers: true)
2720
+ # csv.read # => #<CSV::Table mode:col_or_row row_count:4>
2721
+ #
2722
+ # ---
2723
+ #
2724
+ # Raises an exception if the source is not opened for reading:
2725
+ # string = "foo,0\nbar,1\nbaz,2\n"
2726
+ # csv = CSV.new(string)
2727
+ # csv.close
2728
+ # # Raises IOError (not opened for reading)
2729
+ # csv.read
2730
+ def read
2731
+ rows = to_a
2732
+ if parser.use_headers?
2733
+ Table.new(rows, headers: parser.headers)
2734
+ else
2735
+ rows
2736
+ end
2737
+ end
2738
+ alias_method :readlines, :read
2739
+
2740
+ # :call-seq:
2741
+ # csv.header_row? -> true or false
2742
+ #
2743
+ # Returns +true+ if the next row to be read is a header row\;
2744
+ # +false+ otherwise.
2745
+ #
2746
+ # Without headers:
2747
+ # string = "foo,0\nbar,1\nbaz,2\n"
2748
+ # csv = CSV.new(string)
2749
+ # csv.header_row? # => false
2750
+ #
2751
+ # With headers:
2752
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2753
+ # csv = CSV.new(string, headers: true)
2754
+ # csv.header_row? # => true
2755
+ # csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
2756
+ # csv.header_row? # => false
2757
+ #
2758
+ # ---
2759
+ #
2760
+ # Raises an exception if the source is not opened for reading:
2761
+ # string = "foo,0\nbar,1\nbaz,2\n"
2762
+ # csv = CSV.new(string)
2763
+ # csv.close
2764
+ # # Raises IOError (not opened for reading)
2765
+ # csv.header_row?
2766
+ def header_row?
2767
+ parser.header_row?
2768
+ end
2769
+
2770
+ # :call-seq:
2771
+ # csv.shift -> array, csv_row, or nil
2772
+ #
2773
+ # Returns the next row of data as:
2774
+ # - An \Array if no headers are used.
2775
+ # - A CSV::Row object if headers are used.
2776
+ #
2777
+ # The data source must be opened for reading.
2778
+ #
2779
+ # Without headers:
2780
+ # string = "foo,0\nbar,1\nbaz,2\n"
2781
+ # csv = CSV.new(string)
2782
+ # csv.shift # => ["foo", "0"]
2783
+ # csv.shift # => ["bar", "1"]
2784
+ # csv.shift # => ["baz", "2"]
2785
+ # csv.shift # => nil
2786
+ #
2787
+ # With headers:
2788
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2789
+ # csv = CSV.new(string, headers: true)
2790
+ # csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
2791
+ # csv.shift # => #<CSV::Row "Name":"bar" "Value":"1">
2792
+ # csv.shift # => #<CSV::Row "Name":"baz" "Value":"2">
2793
+ # csv.shift # => nil
2794
+ #
2795
+ # ---
2796
+ #
2797
+ # Raises an exception if the source is not opened for reading:
2798
+ # string = "foo,0\nbar,1\nbaz,2\n"
2799
+ # csv = CSV.new(string)
2800
+ # csv.close
2801
+ # # Raises IOError (not opened for reading)
2802
+ # csv.shift
2803
+ def shift
2804
+ if @eof_error
2805
+ eof_error, @eof_error = @eof_error, nil
2806
+ raise eof_error
2807
+ end
2808
+ begin
2809
+ parser_enumerator.next
2810
+ rescue StopIteration
2811
+ nil
2812
+ end
2813
+ end
2814
+ alias_method :gets, :shift
2815
+ alias_method :readline, :shift
2816
+
2817
+ # :call-seq:
2818
+ # csv.inspect -> string
2819
+ #
2820
+ # Returns a \String showing certain properties of +self+:
2821
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2822
+ # csv = CSV.new(string, headers: true)
2823
+ # s = csv.inspect
2824
+ # s # => "#<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:\",\" row_sep:\"\\n\" quote_char:\"\\\"\" headers:true>"
2825
+ def inspect
2826
+ str = ["#<", self.class.to_s, " io_type:"]
2827
+ # show type of wrapped IO
2828
+ if @io == $stdout then str << "$stdout"
2829
+ elsif @io == $stdin then str << "$stdin"
2830
+ elsif @io == $stderr then str << "$stderr"
2831
+ else str << @io.class.to_s
2832
+ end
2833
+ # show IO.path(), if available
2834
+ if @io.respond_to?(:path) and (p = @io.path)
2835
+ str << " io_path:" << p.inspect
2836
+ end
2837
+ # show encoding
2838
+ str << " encoding:" << @encoding.name
2839
+ # show other attributes
2840
+ ["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name|
2841
+ if a = __send__(attr_name)
2842
+ str << " " << attr_name << ":" << a.inspect
2843
+ end
2844
+ end
2845
+ ["skip_blanks", "liberal_parsing"].each do |attr_name|
2846
+ if a = __send__("#{attr_name}?")
2847
+ str << " " << attr_name << ":" << a.inspect
2848
+ end
2849
+ end
2850
+ _headers = headers
2851
+ str << " headers:" << _headers.inspect if _headers
2852
+ str << ">"
2853
+ begin
2854
+ str.join('')
2855
+ rescue # any encoding error
2856
+ str.map do |s|
2857
+ e = Encoding::Converter.asciicompat_encoding(s.encoding)
2858
+ e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
2859
+ end.join('')
2860
+ end
2861
+ end
2862
+
2863
+ private
2864
+
2865
+ def determine_encoding(encoding, internal_encoding)
2866
+ # honor the IO encoding if we can, otherwise default to ASCII-8BIT
2867
+ io_encoding = raw_encoding
2868
+ return io_encoding if io_encoding
2869
+
2870
+ return Encoding.find(internal_encoding) if internal_encoding
2871
+
2872
+ if encoding
2873
+ encoding, = encoding.split(":", 2) if encoding.is_a?(String)
2874
+ return Encoding.find(encoding)
2875
+ end
2876
+
2877
+ Encoding.default_internal || Encoding.default_external
2878
+ end
2879
+
2880
+ def normalize_converters(converters)
2881
+ converters ||= []
2882
+ unless converters.is_a?(Array)
2883
+ converters = [converters]
2884
+ end
2885
+ converters.collect do |converter|
2886
+ case converter
2887
+ when Proc # custom code block
2888
+ [nil, converter]
2889
+ else # by name
2890
+ [converter, nil]
2891
+ end
2892
+ end
2893
+ end
2894
+
2895
+ #
2896
+ # Processes +fields+ with <tt>@converters</tt>, or <tt>@header_converters</tt>
2897
+ # if +headers+ is passed as +true+, returning the converted field set. Any
2898
+ # converter that changes the field into something other than a String halts
2899
+ # the pipeline of conversion for that field. This is primarily an efficiency
2900
+ # shortcut.
2901
+ #
2902
+ def convert_fields(fields, headers = false)
2903
+ if headers
2904
+ header_fields_converter.convert(fields, nil, 0)
2905
+ else
2906
+ parser_fields_converter.convert(fields, @headers, lineno)
2907
+ end
2908
+ end
2909
+
2910
+ #
2911
+ # Returns the encoding of the internal IO object.
2912
+ #
2913
+ def raw_encoding
2914
+ if @io.respond_to? :internal_encoding
2915
+ @io.internal_encoding || @io.external_encoding
2916
+ elsif @io.respond_to? :encoding
2917
+ @io.encoding
2918
+ else
2919
+ nil
2920
+ end
2921
+ end
2922
+
2923
+ def parser_fields_converter
2924
+ @parser_fields_converter ||= build_parser_fields_converter
2925
+ end
2926
+
2927
+ def build_parser_fields_converter
2928
+ specific_options = {
2929
+ builtin_converters_name: :Converters,
2930
+ }
2931
+ options = @base_fields_converter_options.merge(specific_options)
2932
+ build_fields_converter(@initial_converters, options)
2933
+ end
2934
+
2935
+ def header_fields_converter
2936
+ @header_fields_converter ||= build_header_fields_converter
2937
+ end
2938
+
2939
+ def build_header_fields_converter
2940
+ specific_options = {
2941
+ builtin_converters_name: :HeaderConverters,
2942
+ accept_nil: true,
2943
+ }
2944
+ options = @base_fields_converter_options.merge(specific_options)
2945
+ build_fields_converter(@initial_header_converters, options)
2946
+ end
2947
+
2948
+ def writer_fields_converter
2949
+ @writer_fields_converter ||= build_writer_fields_converter
2950
+ end
2951
+
2952
+ def build_writer_fields_converter
2953
+ build_fields_converter(@initial_write_converters,
2954
+ @write_fields_converter_options)
2955
+ end
2956
+
2957
+ def build_fields_converter(initial_converters, options)
2958
+ fields_converter = FieldsConverter.new(options)
2959
+ normalize_converters(initial_converters).each do |name, converter|
2960
+ fields_converter.add_converter(name, &converter)
2961
+ end
2962
+ fields_converter
2963
+ end
2964
+
2965
+ def parser
2966
+ @parser ||= Parser.new(@io, parser_options)
2967
+ end
2968
+
2969
+ def parser_options
2970
+ @parser_options.merge(header_fields_converter: header_fields_converter,
2971
+ fields_converter: parser_fields_converter)
2972
+ end
2973
+
2974
+ def parser_enumerator
2975
+ @parser_enumerator ||= parser.parse
2976
+ end
2977
+
2978
+ def writer
2979
+ @writer ||= Writer.new(@io, writer_options)
2980
+ end
2981
+
2982
+ def writer_options
2983
+ @writer_options.merge(header_fields_converter: header_fields_converter,
2984
+ fields_converter: writer_fields_converter)
2985
+ end
2986
+ end
2987
+
2988
+ # Passes +args+ to CSV::instance.
2989
+ #
2990
+ # CSV("CSV,data").read
2991
+ # #=> [["CSV", "data"]]
2992
+ #
2993
+ # If a block is given, the instance is passed the block and the return value
2994
+ # becomes the return value of the block.
2995
+ #
2996
+ # CSV("CSV,data") { |c|
2997
+ # c.read.any? { |a| a.include?("data") }
2998
+ # } #=> true
2999
+ #
3000
+ # CSV("CSV,data") { |c|
3001
+ # c.read.any? { |a| a.include?("zombies") }
3002
+ # } #=> false
3003
+ #
3004
+ # CSV options may also be given.
3005
+ #
3006
+ # io = StringIO.new
3007
+ # CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
3008
+ #
3009
+ # This API is not Ractor-safe.
3010
+ #
3011
+ def CSV(*args, **options, &block)
3012
+ CSV.instance(*args, **options, &block)
3013
+ end
3014
+
3015
+ require_relative "csv/version"
3016
+ require_relative "csv/core_ext/array"
3017
+ require_relative "csv/core_ext/string"