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,1292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ require_relative "input_record_separator"
6
+ require_relative "row"
7
+ require_relative "table"
8
+
9
+ class CSV
10
+ # Note: Don't use this class directly. This is an internal class.
11
+ class Parser
12
+ #
13
+ # A CSV::Parser is m17n aware. The parser works in the Encoding of the IO
14
+ # or String object being read from or written to. Your data is never transcoded
15
+ # (unless you ask Ruby to transcode it for you) and will literally be parsed in
16
+ # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
17
+ # Encoding of your data. This is accomplished by transcoding the parser itself
18
+ # into your Encoding.
19
+ #
20
+
21
+ # Raised when encoding is invalid.
22
+ class InvalidEncoding < StandardError
23
+ end
24
+
25
+ # Raised when unexpected case is happen.
26
+ class UnexpectedError < StandardError
27
+ end
28
+
29
+ #
30
+ # CSV::Scanner receives a CSV output, scans it and return the content.
31
+ # It also controls the life cycle of the object with its methods +keep_start+,
32
+ # +keep_end+, +keep_back+, +keep_drop+.
33
+ #
34
+ # Uses StringScanner (the official strscan gem). Strscan provides lexical
35
+ # scanning operations on a String. We inherit its object and take advantage
36
+ # on the methods. For more information, please visit:
37
+ # https://ruby-doc.org/stdlib-2.6.1/libdoc/strscan/rdoc/StringScanner.html
38
+ #
39
+ class Scanner < StringScanner
40
+ alias_method :scan_all, :scan
41
+
42
+ def initialize(*args)
43
+ super
44
+ @keeps = []
45
+ end
46
+
47
+ def each_line(row_separator)
48
+ position = pos
49
+ rest.each_line(row_separator) do |line|
50
+ position += line.bytesize
51
+ self.pos = position
52
+ yield(line)
53
+ end
54
+ end
55
+
56
+ def keep_start
57
+ @keeps.push(pos)
58
+ end
59
+
60
+ def keep_end
61
+ start = @keeps.pop
62
+ string.byteslice(start, pos - start)
63
+ end
64
+
65
+ def keep_back
66
+ self.pos = @keeps.pop
67
+ end
68
+
69
+ def keep_drop
70
+ @keeps.pop
71
+ end
72
+ end
73
+
74
+ #
75
+ # CSV::InputsScanner receives IO inputs, encoding and the chunk_size.
76
+ # It also controls the life cycle of the object with its methods +keep_start+,
77
+ # +keep_end+, +keep_back+, +keep_drop+.
78
+ #
79
+ # CSV::InputsScanner.scan() tries to match with pattern at the current position.
80
+ # If there's a match, the scanner advances the "scan pointer" and returns the matched string.
81
+ # Otherwise, the scanner returns nil.
82
+ #
83
+ # CSV::InputsScanner.rest() returns the "rest" of the string (i.e. everything after the scan pointer).
84
+ # If there is no more data (eos? = true), it returns "".
85
+ #
86
+ class InputsScanner
87
+ def initialize(inputs, encoding, row_separator, chunk_size: 8192)
88
+ @inputs = inputs.dup
89
+ @encoding = encoding
90
+ @row_separator = row_separator
91
+ @chunk_size = chunk_size
92
+ @last_scanner = @inputs.empty?
93
+ @keeps = []
94
+ read_chunk
95
+ end
96
+
97
+ def each_line(row_separator)
98
+ return enum_for(__method__, row_separator) unless block_given?
99
+ buffer = nil
100
+ input = @scanner.rest
101
+ position = @scanner.pos
102
+ offset = 0
103
+ n_row_separator_chars = row_separator.size
104
+ # trace(__method__, :start, input)
105
+ while true
106
+ input.each_line(row_separator) do |line|
107
+ @scanner.pos += line.bytesize
108
+ if buffer
109
+ if n_row_separator_chars == 2 and
110
+ buffer.end_with?(row_separator[0]) and
111
+ line.start_with?(row_separator[1])
112
+ buffer << line[0]
113
+ line = line[1..-1]
114
+ position += buffer.bytesize + offset
115
+ @scanner.pos = position
116
+ offset = 0
117
+ yield(buffer)
118
+ buffer = nil
119
+ next if line.empty?
120
+ else
121
+ buffer << line
122
+ line = buffer
123
+ buffer = nil
124
+ end
125
+ end
126
+ if line.end_with?(row_separator)
127
+ position += line.bytesize + offset
128
+ @scanner.pos = position
129
+ offset = 0
130
+ yield(line)
131
+ else
132
+ buffer = line
133
+ end
134
+ end
135
+ break unless read_chunk
136
+ input = @scanner.rest
137
+ position = @scanner.pos
138
+ offset = -buffer.bytesize if buffer
139
+ end
140
+ yield(buffer) if buffer
141
+ end
142
+
143
+ def scan(pattern)
144
+ # trace(__method__, pattern, :start)
145
+ value = @scanner.scan(pattern)
146
+ # trace(__method__, pattern, :done, :last, value) if @last_scanner
147
+ return value if @last_scanner
148
+
149
+ read_chunk if value and @scanner.eos?
150
+ # trace(__method__, pattern, :done, value)
151
+ value
152
+ end
153
+
154
+ def scan_all(pattern)
155
+ # trace(__method__, pattern, :start)
156
+ value = @scanner.scan(pattern)
157
+ # trace(__method__, pattern, :done, :last, value) if @last_scanner
158
+ return value if @last_scanner
159
+
160
+ # trace(__method__, pattern, :done, :nil) if value.nil?
161
+ return nil if value.nil?
162
+ while @scanner.eos? and read_chunk and (sub_value = @scanner.scan(pattern))
163
+ # trace(__method__, pattern, :sub, sub_value)
164
+ value << sub_value
165
+ end
166
+ # trace(__method__, pattern, :done, value)
167
+ value
168
+ end
169
+
170
+ def eos?
171
+ @scanner.eos?
172
+ end
173
+
174
+ def keep_start
175
+ # trace(__method__, :start)
176
+ adjust_last_keep
177
+ @keeps.push([@scanner, @scanner.pos, nil])
178
+ # trace(__method__, :done)
179
+ end
180
+
181
+ def keep_end
182
+ # trace(__method__, :start)
183
+ scanner, start, buffer = @keeps.pop
184
+ if scanner == @scanner
185
+ keep = @scanner.string.byteslice(start, @scanner.pos - start)
186
+ else
187
+ keep = @scanner.string.byteslice(0, @scanner.pos)
188
+ end
189
+ if buffer
190
+ buffer << keep
191
+ keep = buffer
192
+ end
193
+ # trace(__method__, :done, keep)
194
+ keep
195
+ end
196
+
197
+ def keep_back
198
+ # trace(__method__, :start)
199
+ scanner, start, buffer = @keeps.pop
200
+ if buffer
201
+ # trace(__method__, :rescan, start, buffer)
202
+ string = @scanner.string
203
+ if scanner == @scanner
204
+ keep = string.byteslice(start,
205
+ string.bytesize - @scanner.pos - start)
206
+ else
207
+ keep = string
208
+ end
209
+ if keep and not keep.empty?
210
+ @inputs.unshift(StringIO.new(keep))
211
+ @last_scanner = false
212
+ end
213
+ @scanner = StringScanner.new(buffer)
214
+ else
215
+ if @scanner != scanner
216
+ message = "scanners are different but no buffer: "
217
+ message += "#{@scanner.inspect}(#{@scanner.object_id}): "
218
+ message += "#{scanner.inspect}(#{scanner.object_id})"
219
+ raise UnexpectedError, message
220
+ end
221
+ # trace(__method__, :repos, start, buffer)
222
+ @scanner.pos = start
223
+ last_scanner, last_start, last_buffer = @keeps.last
224
+ # Drop the last buffer when the last buffer is the same data
225
+ # in the last keep. If we keep it, we have duplicated data
226
+ # by the next keep_back.
227
+ if last_scanner == @scanner and
228
+ last_buffer and
229
+ last_buffer == last_scanner.string.byteslice(last_start, start)
230
+ @keeps.last[2] = nil
231
+ end
232
+ end
233
+ read_chunk if @scanner.eos?
234
+ end
235
+
236
+ def keep_drop
237
+ _, _, buffer = @keeps.pop
238
+ # trace(__method__, :done, :empty) unless buffer
239
+ return unless buffer
240
+
241
+ last_keep = @keeps.last
242
+ # trace(__method__, :done, :no_last_keep) unless last_keep
243
+ return unless last_keep
244
+
245
+ if last_keep[2]
246
+ last_keep[2] << buffer
247
+ else
248
+ last_keep[2] = buffer
249
+ end
250
+ # trace(__method__, :done)
251
+ end
252
+
253
+ def rest
254
+ @scanner.rest
255
+ end
256
+
257
+ def check(pattern)
258
+ @scanner.check(pattern)
259
+ end
260
+
261
+ private
262
+ def trace(*args)
263
+ pp([*args, @scanner, @scanner&.string, @scanner&.pos, @keeps])
264
+ end
265
+
266
+ def adjust_last_keep
267
+ # trace(__method__, :start)
268
+
269
+ keep = @keeps.last
270
+ # trace(__method__, :done, :empty) if keep.nil?
271
+ return if keep.nil?
272
+
273
+ scanner, start, buffer = keep
274
+ string = @scanner.string
275
+ if @scanner != scanner
276
+ start = 0
277
+ end
278
+ if start == 0 and @scanner.eos?
279
+ keep_data = string
280
+ else
281
+ keep_data = string.byteslice(start, @scanner.pos - start)
282
+ end
283
+ if keep_data
284
+ if buffer
285
+ buffer << keep_data
286
+ else
287
+ keep[2] = keep_data.dup
288
+ end
289
+ end
290
+
291
+ # trace(__method__, :done)
292
+ end
293
+
294
+ def read_chunk
295
+ return false if @last_scanner
296
+
297
+ adjust_last_keep
298
+
299
+ input = @inputs.first
300
+ case input
301
+ when StringIO
302
+ string = input.read
303
+ raise InvalidEncoding unless string.valid_encoding?
304
+ # trace(__method__, :stringio, string)
305
+ @scanner = StringScanner.new(string)
306
+ @inputs.shift
307
+ @last_scanner = @inputs.empty?
308
+ true
309
+ else
310
+ chunk = input.gets(@row_separator, @chunk_size)
311
+ if chunk
312
+ raise InvalidEncoding unless chunk.valid_encoding?
313
+ # trace(__method__, :chunk, chunk)
314
+ @scanner = StringScanner.new(chunk)
315
+ if input.respond_to?(:eof?) and input.eof?
316
+ @inputs.shift
317
+ @last_scanner = @inputs.empty?
318
+ end
319
+ true
320
+ else
321
+ # trace(__method__, :no_chunk)
322
+ @scanner = StringScanner.new("".encode(@encoding))
323
+ @inputs.shift
324
+ @last_scanner = @inputs.empty?
325
+ if @last_scanner
326
+ false
327
+ else
328
+ read_chunk
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ def initialize(input, options)
336
+ @input = input
337
+ @options = options
338
+ @samples = []
339
+
340
+ prepare
341
+ end
342
+
343
+ def column_separator
344
+ @column_separator
345
+ end
346
+
347
+ def row_separator
348
+ @row_separator
349
+ end
350
+
351
+ def quote_character
352
+ @quote_character
353
+ end
354
+
355
+ def field_size_limit
356
+ @max_field_size&.succ
357
+ end
358
+
359
+ def max_field_size
360
+ @max_field_size
361
+ end
362
+
363
+ def skip_lines
364
+ @skip_lines
365
+ end
366
+
367
+ def unconverted_fields?
368
+ @unconverted_fields
369
+ end
370
+
371
+ def headers
372
+ @headers
373
+ end
374
+
375
+ def header_row?
376
+ @use_headers and @headers.nil?
377
+ end
378
+
379
+ def return_headers?
380
+ @return_headers
381
+ end
382
+
383
+ def skip_blanks?
384
+ @skip_blanks
385
+ end
386
+
387
+ def liberal_parsing?
388
+ @liberal_parsing
389
+ end
390
+
391
+ def lineno
392
+ @lineno
393
+ end
394
+
395
+ def line
396
+ last_line
397
+ end
398
+
399
+ def parse(&block)
400
+ return to_enum(__method__) unless block_given?
401
+
402
+ if @return_headers and @headers and @raw_headers
403
+ headers = Row.new(@headers, @raw_headers, true)
404
+ if @unconverted_fields
405
+ headers = add_unconverted_fields(headers, [])
406
+ end
407
+ yield headers
408
+ end
409
+
410
+ begin
411
+ @scanner ||= build_scanner
412
+ __send__(@parse_method, &block)
413
+ rescue InvalidEncoding
414
+ if @scanner
415
+ ignore_broken_line
416
+ lineno = @lineno
417
+ else
418
+ lineno = @lineno + 1
419
+ end
420
+ raise InvalidEncodingError.new(@encoding, lineno)
421
+ rescue UnexpectedError => error
422
+ if @scanner
423
+ ignore_broken_line
424
+ lineno = @lineno
425
+ else
426
+ lineno = @lineno + 1
427
+ end
428
+ message = "This should not be happen: #{error.message}: "
429
+ message += "Please report this to https://github.com/ruby/csv/issues"
430
+ raise MalformedCSVError.new(message, lineno)
431
+ end
432
+ end
433
+
434
+ def use_headers?
435
+ @use_headers
436
+ end
437
+
438
+ private
439
+ # A set of tasks to prepare the file in order to parse it
440
+ def prepare
441
+ prepare_variable
442
+ prepare_quote_character
443
+ prepare_backslash
444
+ prepare_skip_lines
445
+ prepare_strip
446
+ prepare_separators
447
+ validate_strip_and_col_sep_options
448
+ prepare_quoted
449
+ prepare_unquoted
450
+ prepare_line
451
+ prepare_header
452
+ prepare_parser
453
+ end
454
+
455
+ def prepare_variable
456
+ @encoding = @options[:encoding]
457
+ liberal_parsing = @options[:liberal_parsing]
458
+ if liberal_parsing
459
+ @liberal_parsing = true
460
+ if liberal_parsing.is_a?(Hash)
461
+ @double_quote_outside_quote =
462
+ liberal_parsing[:double_quote_outside_quote]
463
+ @backslash_quote = liberal_parsing[:backslash_quote]
464
+ else
465
+ @double_quote_outside_quote = false
466
+ @backslash_quote = false
467
+ end
468
+ else
469
+ @liberal_parsing = false
470
+ @backslash_quote = false
471
+ end
472
+ @unconverted_fields = @options[:unconverted_fields]
473
+ @max_field_size = @options[:max_field_size]
474
+ @skip_blanks = @options[:skip_blanks]
475
+ @fields_converter = @options[:fields_converter]
476
+ @header_fields_converter = @options[:header_fields_converter]
477
+ end
478
+
479
+ def prepare_quote_character
480
+ @quote_character = @options[:quote_character]
481
+ if @quote_character.nil?
482
+ @escaped_quote_character = nil
483
+ @escaped_quote = nil
484
+ else
485
+ @quote_character = @quote_character.to_s.encode(@encoding)
486
+ if @quote_character.length != 1
487
+ message = ":quote_char has to be nil or a single character String"
488
+ raise ArgumentError, message
489
+ end
490
+ @escaped_quote_character = Regexp.escape(@quote_character)
491
+ @escaped_quote = Regexp.new(@escaped_quote_character)
492
+ end
493
+ end
494
+
495
+ def prepare_backslash
496
+ return unless @backslash_quote
497
+
498
+ @backslash_character = "\\".encode(@encoding)
499
+
500
+ @escaped_backslash_character = Regexp.escape(@backslash_character)
501
+ @escaped_backslash = Regexp.new(@escaped_backslash_character)
502
+ if @quote_character.nil?
503
+ @backslash_quote_character = nil
504
+ else
505
+ @backslash_quote_character =
506
+ @backslash_character + @escaped_quote_character
507
+ end
508
+ end
509
+
510
+ def prepare_skip_lines
511
+ skip_lines = @options[:skip_lines]
512
+ case skip_lines
513
+ when String
514
+ @skip_lines = skip_lines.encode(@encoding)
515
+ when Regexp, nil
516
+ @skip_lines = skip_lines
517
+ else
518
+ unless skip_lines.respond_to?(:match)
519
+ message =
520
+ ":skip_lines has to respond to \#match: #{skip_lines.inspect}"
521
+ raise ArgumentError, message
522
+ end
523
+ @skip_lines = skip_lines
524
+ end
525
+ end
526
+
527
+ def prepare_strip
528
+ @strip = @options[:strip]
529
+ @escaped_strip = nil
530
+ @strip_value = nil
531
+ @rstrip_value = nil
532
+ if @strip.is_a?(String)
533
+ case @strip.length
534
+ when 0
535
+ raise ArgumentError, ":strip must not be an empty String"
536
+ when 1
537
+ # ok
538
+ else
539
+ raise ArgumentError, ":strip doesn't support 2 or more characters yet"
540
+ end
541
+ @strip = @strip.encode(@encoding)
542
+ @escaped_strip = Regexp.escape(@strip)
543
+ if @quote_character
544
+ @strip_value = Regexp.new(@escaped_strip +
545
+ "+".encode(@encoding))
546
+ @rstrip_value = Regexp.new(@escaped_strip +
547
+ "+\\z".encode(@encoding))
548
+ end
549
+ elsif @strip
550
+ strip_values = " \t\f\v"
551
+ @escaped_strip = strip_values.encode(@encoding)
552
+ if @quote_character
553
+ @strip_value = Regexp.new("[#{strip_values}]+".encode(@encoding))
554
+ @rstrip_value = Regexp.new("[#{strip_values}]+\\z".encode(@encoding))
555
+ end
556
+ end
557
+ end
558
+
559
+ begin
560
+ StringScanner.new("x").scan("x")
561
+ rescue TypeError
562
+ STRING_SCANNER_SCAN_ACCEPT_STRING = false
563
+ else
564
+ STRING_SCANNER_SCAN_ACCEPT_STRING = true
565
+ end
566
+
567
+ def prepare_separators
568
+ column_separator = @options[:column_separator]
569
+ @column_separator = column_separator.to_s.encode(@encoding)
570
+ if @column_separator.size < 1
571
+ message = ":col_sep must be 1 or more characters: "
572
+ message += column_separator.inspect
573
+ raise ArgumentError, message
574
+ end
575
+ @row_separator =
576
+ resolve_row_separator(@options[:row_separator]).encode(@encoding)
577
+
578
+ @escaped_column_separator = Regexp.escape(@column_separator)
579
+ @escaped_first_column_separator = Regexp.escape(@column_separator[0])
580
+ if @column_separator.size > 1
581
+ @column_end = Regexp.new(@escaped_column_separator)
582
+ @column_ends = @column_separator.each_char.collect do |char|
583
+ Regexp.new(Regexp.escape(char))
584
+ end
585
+ @first_column_separators = Regexp.new(@escaped_first_column_separator +
586
+ "+".encode(@encoding))
587
+ else
588
+ if STRING_SCANNER_SCAN_ACCEPT_STRING
589
+ @column_end = @column_separator
590
+ else
591
+ @column_end = Regexp.new(@escaped_column_separator)
592
+ end
593
+ @column_ends = nil
594
+ @first_column_separators = nil
595
+ end
596
+
597
+ escaped_row_separator = Regexp.escape(@row_separator)
598
+ @row_end = Regexp.new(escaped_row_separator)
599
+ if @row_separator.size > 1
600
+ @row_ends = @row_separator.each_char.collect do |char|
601
+ Regexp.new(Regexp.escape(char))
602
+ end
603
+ else
604
+ @row_ends = nil
605
+ end
606
+
607
+ @cr = "\r".encode(@encoding)
608
+ @lf = "\n".encode(@encoding)
609
+ @line_end = Regexp.new("\r\n|\n|\r".encode(@encoding))
610
+ @not_line_end = Regexp.new("[^\r\n]+".encode(@encoding))
611
+ end
612
+
613
+ # This method verifies that there are no (obvious) ambiguities with the
614
+ # provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+
615
+ # and +strip+ were both equal to +\t+, then there would be no clear way to
616
+ # parse the input.
617
+ def validate_strip_and_col_sep_options
618
+ return unless @strip
619
+
620
+ if @strip.is_a?(String)
621
+ if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip)
622
+ raise ArgumentError,
623
+ "The provided strip (#{@escaped_strip}) and " \
624
+ "col_sep (#{@escaped_column_separator}) options are incompatible."
625
+ end
626
+ else
627
+ if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator)
628
+ raise ArgumentError,
629
+ "The provided strip (true) and " \
630
+ "col_sep (#{@escaped_column_separator}) options are incompatible."
631
+ end
632
+ end
633
+ end
634
+
635
+ def prepare_quoted
636
+ if @quote_character
637
+ @quotes = Regexp.new(@escaped_quote_character +
638
+ "+".encode(@encoding))
639
+ no_quoted_values = @escaped_quote_character.dup
640
+ if @backslash_quote
641
+ no_quoted_values << @escaped_backslash_character
642
+ end
643
+ @quoted_value = Regexp.new("[^".encode(@encoding) +
644
+ no_quoted_values +
645
+ "]+".encode(@encoding))
646
+ end
647
+ if @escaped_strip
648
+ @split_column_separator = Regexp.new(@escaped_strip +
649
+ "*".encode(@encoding) +
650
+ @escaped_column_separator +
651
+ @escaped_strip +
652
+ "*".encode(@encoding))
653
+ else
654
+ if @column_separator == " ".encode(@encoding)
655
+ @split_column_separator = Regexp.new(@escaped_column_separator)
656
+ else
657
+ @split_column_separator = @column_separator
658
+ end
659
+ end
660
+ end
661
+
662
+ def prepare_unquoted
663
+ return if @quote_character.nil?
664
+
665
+ no_unquoted_values = "\r\n".encode(@encoding)
666
+ no_unquoted_values << @escaped_first_column_separator
667
+ unless @liberal_parsing
668
+ no_unquoted_values << @escaped_quote_character
669
+ end
670
+ @unquoted_value = Regexp.new("[^".encode(@encoding) +
671
+ no_unquoted_values +
672
+ "]+".encode(@encoding))
673
+ end
674
+
675
+ def resolve_row_separator(separator)
676
+ if separator == :auto
677
+ cr = "\r".encode(@encoding)
678
+ lf = "\n".encode(@encoding)
679
+ if @input.is_a?(StringIO)
680
+ pos = @input.pos
681
+ separator = detect_row_separator(@input.read, cr, lf)
682
+ @input.seek(pos)
683
+ elsif @input.respond_to?(:gets)
684
+ if @input.is_a?(File)
685
+ chunk_size = 32 * 1024
686
+ else
687
+ chunk_size = 1024
688
+ end
689
+ begin
690
+ while separator == :auto
691
+ #
692
+ # if we run out of data, it's probably a single line
693
+ # (ensure will set default value)
694
+ #
695
+ break unless sample = @input.gets(nil, chunk_size)
696
+
697
+ # extend sample if we're unsure of the line ending
698
+ if sample.end_with?(cr)
699
+ sample << (@input.gets(nil, 1) || "")
700
+ end
701
+
702
+ @samples << sample
703
+
704
+ separator = detect_row_separator(sample, cr, lf)
705
+ end
706
+ rescue IOError
707
+ # do nothing: ensure will set default
708
+ end
709
+ end
710
+ separator = InputRecordSeparator.value if separator == :auto
711
+ end
712
+ separator.to_s.encode(@encoding)
713
+ end
714
+
715
+ def detect_row_separator(sample, cr, lf)
716
+ lf_index = sample.index(lf)
717
+ if lf_index
718
+ cr_index = sample[0, lf_index].index(cr)
719
+ else
720
+ cr_index = sample.index(cr)
721
+ end
722
+ if cr_index and lf_index
723
+ if cr_index + 1 == lf_index
724
+ cr + lf
725
+ elsif cr_index < lf_index
726
+ cr
727
+ else
728
+ lf
729
+ end
730
+ elsif cr_index
731
+ cr
732
+ elsif lf_index
733
+ lf
734
+ else
735
+ :auto
736
+ end
737
+ end
738
+
739
+ def prepare_line
740
+ @lineno = 0
741
+ @last_line = nil
742
+ @scanner = nil
743
+ end
744
+
745
+ def last_line
746
+ if @scanner
747
+ @last_line ||= @scanner.keep_end
748
+ else
749
+ @last_line
750
+ end
751
+ end
752
+
753
+ def prepare_header
754
+ @return_headers = @options[:return_headers]
755
+
756
+ headers = @options[:headers]
757
+ case headers
758
+ when Array
759
+ @raw_headers = headers
760
+ quoted_fields = FieldsConverter::NO_QUOTED_FIELDS
761
+ @use_headers = true
762
+ when String
763
+ @raw_headers, quoted_fields = parse_headers(headers)
764
+ @use_headers = true
765
+ when nil, false
766
+ @raw_headers = nil
767
+ @use_headers = false
768
+ else
769
+ @raw_headers = nil
770
+ @use_headers = true
771
+ end
772
+ if @raw_headers
773
+ @headers = adjust_headers(@raw_headers, quoted_fields)
774
+ else
775
+ @headers = nil
776
+ end
777
+ end
778
+
779
+ def parse_headers(row)
780
+ quoted_fields = []
781
+ converter = lambda do |field, info|
782
+ quoted_fields << info.quoted?
783
+ field
784
+ end
785
+ headers = CSV.parse_line(row,
786
+ col_sep: @column_separator,
787
+ row_sep: @row_separator,
788
+ quote_char: @quote_character,
789
+ converters: [converter])
790
+ [headers, quoted_fields]
791
+ end
792
+
793
+ def adjust_headers(headers, quoted_fields)
794
+ adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno, quoted_fields)
795
+ adjusted_headers.each {|h| h.freeze if h.is_a? String}
796
+ adjusted_headers
797
+ end
798
+
799
+ def prepare_parser
800
+ @may_quoted = may_quoted?
801
+ if @quote_character.nil?
802
+ @parse_method = :parse_no_quote
803
+ elsif @liberal_parsing or @strip
804
+ @parse_method = :parse_quotable_robust
805
+ else
806
+ @parse_method = :parse_quotable_loose
807
+ end
808
+ end
809
+
810
+ def may_quoted?
811
+ return false if @quote_character.nil?
812
+
813
+ if @input.is_a?(StringIO)
814
+ pos = @input.pos
815
+ sample = @input.read
816
+ @input.seek(pos)
817
+ else
818
+ return false if @samples.empty?
819
+ sample = @samples.first
820
+ end
821
+ sample[0, 128].index(@quote_character)
822
+ end
823
+
824
+ class UnoptimizedStringIO # :nodoc:
825
+ def initialize(string)
826
+ @io = StringIO.new(string, "rb:#{string.encoding}")
827
+ end
828
+
829
+ def gets(*args)
830
+ @io.gets(*args)
831
+ end
832
+
833
+ def each_line(*args, &block)
834
+ @io.each_line(*args, &block)
835
+ end
836
+
837
+ def eof?
838
+ @io.eof?
839
+ end
840
+ end
841
+
842
+ SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes")
843
+ if SCANNER_TEST
844
+ SCANNER_TEST_CHUNK_SIZE_NAME = "CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"
845
+ SCANNER_TEST_CHUNK_SIZE_VALUE = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
846
+ def build_scanner
847
+ inputs = @samples.collect do |sample|
848
+ UnoptimizedStringIO.new(sample)
849
+ end
850
+ if @input.is_a?(StringIO)
851
+ inputs << UnoptimizedStringIO.new(@input.read)
852
+ else
853
+ inputs << @input
854
+ end
855
+ begin
856
+ chunk_size_value = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
857
+ rescue # Ractor::IsolationError
858
+ # Ractor on Ruby 3.0 can't read ENV value.
859
+ chunk_size_value = SCANNER_TEST_CHUNK_SIZE_VALUE
860
+ end
861
+ chunk_size = Integer((chunk_size_value || "1"), 10)
862
+ InputsScanner.new(inputs,
863
+ @encoding,
864
+ @row_separator,
865
+ chunk_size: chunk_size)
866
+ end
867
+ else
868
+ def build_scanner
869
+ string = nil
870
+ if @samples.empty? and @input.is_a?(StringIO)
871
+ string = @input.read
872
+ elsif @samples.size == 1 and
873
+ @input != ARGF and
874
+ @input.respond_to?(:eof?) and
875
+ @input.eof?
876
+ string = @samples[0]
877
+ end
878
+ if string
879
+ unless string.valid_encoding?
880
+ index = string.lines(@row_separator).index do |line|
881
+ !line.valid_encoding?
882
+ end
883
+ if index
884
+ raise InvalidEncodingError.new(@encoding, @lineno + index + 1)
885
+ end
886
+ end
887
+ Scanner.new(string)
888
+ else
889
+ inputs = @samples.collect do |sample|
890
+ StringIO.new(sample)
891
+ end
892
+ inputs << @input
893
+ InputsScanner.new(inputs, @encoding, @row_separator)
894
+ end
895
+ end
896
+ end
897
+
898
+ def skip_needless_lines
899
+ return unless @skip_lines
900
+
901
+ until @scanner.eos?
902
+ @scanner.keep_start
903
+ line = @scanner.scan_all(@not_line_end) || "".encode(@encoding)
904
+ line << @row_separator if parse_row_end
905
+ if skip_line?(line)
906
+ @lineno += 1
907
+ @scanner.keep_drop
908
+ else
909
+ @scanner.keep_back
910
+ return
911
+ end
912
+ end
913
+ end
914
+
915
+ def skip_line?(line)
916
+ line = line.delete_suffix(@row_separator)
917
+ case @skip_lines
918
+ when String
919
+ line.include?(@skip_lines)
920
+ when Regexp
921
+ @skip_lines.match?(line)
922
+ else
923
+ @skip_lines.match(line)
924
+ end
925
+ end
926
+
927
+ def validate_field_size(field)
928
+ return unless @max_field_size
929
+ return if field.size <= @max_field_size
930
+ ignore_broken_line
931
+ message = "Field size exceeded: #{field.size} > #{@max_field_size}"
932
+ raise MalformedCSVError.new(message, @lineno)
933
+ end
934
+
935
+ def parse_no_quote(&block)
936
+ @scanner.each_line(@row_separator) do |line|
937
+ next if @skip_lines and skip_line?(line)
938
+ original_line = line
939
+ line = line.delete_suffix(@row_separator)
940
+
941
+ if line.empty?
942
+ next if @skip_blanks
943
+ row = []
944
+ else
945
+ line = strip_value(line)
946
+ row = line.split(@split_column_separator, -1)
947
+ if @max_field_size
948
+ row.each do |column|
949
+ validate_field_size(column)
950
+ end
951
+ end
952
+ n_columns = row.size
953
+ i = 0
954
+ while i < n_columns
955
+ row[i] = nil if row[i].empty?
956
+ i += 1
957
+ end
958
+ end
959
+ @last_line = original_line
960
+ emit_row(row, &block)
961
+ end
962
+ end
963
+
964
+ def parse_quotable_loose(&block)
965
+ @scanner.keep_start
966
+ @scanner.each_line(@row_separator) do |line|
967
+ if @skip_lines and skip_line?(line)
968
+ @scanner.keep_drop
969
+ @scanner.keep_start
970
+ next
971
+ end
972
+ original_line = line
973
+ line = line.delete_suffix(@row_separator)
974
+
975
+ if line.empty?
976
+ if @skip_blanks
977
+ @scanner.keep_drop
978
+ @scanner.keep_start
979
+ next
980
+ end
981
+ row = []
982
+ quoted_fields = FieldsConverter::NO_QUOTED_FIELDS
983
+ elsif line.include?(@cr) or line.include?(@lf)
984
+ @scanner.keep_back
985
+ @parse_method = :parse_quotable_robust
986
+ return parse_quotable_robust(&block)
987
+ else
988
+ row = line.split(@split_column_separator, -1)
989
+ quoted_fields = []
990
+ n_columns = row.size
991
+ i = 0
992
+ while i < n_columns
993
+ column = row[i]
994
+ if column.empty?
995
+ quoted_fields << false
996
+ row[i] = nil
997
+ else
998
+ n_quotes = column.count(@quote_character)
999
+ if n_quotes.zero?
1000
+ quoted_fields << false
1001
+ # no quote
1002
+ elsif n_quotes == 2 and
1003
+ column.start_with?(@quote_character) and
1004
+ column.end_with?(@quote_character)
1005
+ quoted_fields << true
1006
+ row[i] = column[1..-2]
1007
+ else
1008
+ @scanner.keep_back
1009
+ @parse_method = :parse_quotable_robust
1010
+ return parse_quotable_robust(&block)
1011
+ end
1012
+ validate_field_size(row[i])
1013
+ end
1014
+ i += 1
1015
+ end
1016
+ end
1017
+ @scanner.keep_drop
1018
+ @scanner.keep_start
1019
+ @last_line = original_line
1020
+ emit_row(row, quoted_fields, &block)
1021
+ end
1022
+ @scanner.keep_drop
1023
+ end
1024
+
1025
+ def parse_quotable_robust(&block)
1026
+ row = []
1027
+ quoted_fields = []
1028
+ skip_needless_lines
1029
+ start_row
1030
+ while true
1031
+ @quoted_column_value = false
1032
+ @unquoted_column_value = false
1033
+ @scanner.scan_all(@strip_value) if @strip_value
1034
+ value = parse_column_value
1035
+ if value
1036
+ @scanner.scan_all(@strip_value) if @strip_value
1037
+ validate_field_size(value)
1038
+ end
1039
+ if parse_column_end
1040
+ row << value
1041
+ quoted_fields << @quoted_column_value
1042
+ elsif parse_row_end
1043
+ if row.empty? and value.nil?
1044
+ emit_row([], &block) unless @skip_blanks
1045
+ else
1046
+ row << value
1047
+ quoted_fields << @quoted_column_value
1048
+ emit_row(row, quoted_fields, &block)
1049
+ row = []
1050
+ quoted_fields.clear
1051
+ end
1052
+ skip_needless_lines
1053
+ start_row
1054
+ elsif @scanner.eos?
1055
+ break if row.empty? and value.nil?
1056
+ row << value
1057
+ quoted_fields << @quoted_column_value
1058
+ emit_row(row, quoted_fields, &block)
1059
+ break
1060
+ else
1061
+ if @quoted_column_value
1062
+ if liberal_parsing? and (new_line = @scanner.check(@line_end))
1063
+ message =
1064
+ "Illegal end-of-line sequence outside of a quoted field " +
1065
+ "<#{new_line.inspect}>"
1066
+ else
1067
+ message = "Any value after quoted field isn't allowed"
1068
+ end
1069
+ ignore_broken_line
1070
+ raise MalformedCSVError.new(message, @lineno)
1071
+ elsif @unquoted_column_value and
1072
+ (new_line = @scanner.scan(@line_end))
1073
+ ignore_broken_line
1074
+ message = "Unquoted fields do not allow new line " +
1075
+ "<#{new_line.inspect}>"
1076
+ raise MalformedCSVError.new(message, @lineno)
1077
+ elsif @scanner.rest.start_with?(@quote_character)
1078
+ ignore_broken_line
1079
+ message = "Illegal quoting"
1080
+ raise MalformedCSVError.new(message, @lineno)
1081
+ elsif (new_line = @scanner.scan(@line_end))
1082
+ ignore_broken_line
1083
+ message = "New line must be <#{@row_separator.inspect}> " +
1084
+ "not <#{new_line.inspect}>"
1085
+ raise MalformedCSVError.new(message, @lineno)
1086
+ else
1087
+ ignore_broken_line
1088
+ raise MalformedCSVError.new("TODO: Meaningful message",
1089
+ @lineno)
1090
+ end
1091
+ end
1092
+ end
1093
+ end
1094
+
1095
+ def parse_column_value
1096
+ if @liberal_parsing
1097
+ quoted_value = parse_quoted_column_value
1098
+ if quoted_value
1099
+ @scanner.scan_all(@strip_value) if @strip_value
1100
+ unquoted_value = parse_unquoted_column_value
1101
+ if unquoted_value
1102
+ if @double_quote_outside_quote
1103
+ unquoted_value = unquoted_value.gsub(@quote_character * 2,
1104
+ @quote_character)
1105
+ if quoted_value.empty? # %Q{""...} case
1106
+ return @quote_character + unquoted_value
1107
+ end
1108
+ end
1109
+ @quote_character + quoted_value + @quote_character + unquoted_value
1110
+ else
1111
+ quoted_value
1112
+ end
1113
+ else
1114
+ parse_unquoted_column_value
1115
+ end
1116
+ elsif @may_quoted
1117
+ parse_quoted_column_value ||
1118
+ parse_unquoted_column_value
1119
+ else
1120
+ parse_unquoted_column_value ||
1121
+ parse_quoted_column_value
1122
+ end
1123
+ end
1124
+
1125
+ def parse_unquoted_column_value
1126
+ value = @scanner.scan_all(@unquoted_value)
1127
+ return nil unless value
1128
+
1129
+ @unquoted_column_value = true
1130
+ if @first_column_separators
1131
+ while true
1132
+ @scanner.keep_start
1133
+ is_column_end = @column_ends.all? do |column_end|
1134
+ @scanner.scan(column_end)
1135
+ end
1136
+ @scanner.keep_back
1137
+ break if is_column_end
1138
+ sub_separator = @scanner.scan_all(@first_column_separators)
1139
+ break if sub_separator.nil?
1140
+ value << sub_separator
1141
+ sub_value = @scanner.scan_all(@unquoted_value)
1142
+ break if sub_value.nil?
1143
+ value << sub_value
1144
+ end
1145
+ end
1146
+ value.gsub!(@backslash_quote_character, @quote_character) if @backslash_quote
1147
+ if @rstrip_value
1148
+ value.gsub!(@rstrip_value, "")
1149
+ end
1150
+ value
1151
+ end
1152
+
1153
+ def parse_quoted_column_value
1154
+ quotes = @scanner.scan_all(@quotes)
1155
+ return nil unless quotes
1156
+
1157
+ @quoted_column_value = true
1158
+ n_quotes = quotes.size
1159
+ if (n_quotes % 2).zero?
1160
+ quotes[0, (n_quotes - 2) / 2]
1161
+ else
1162
+ value = quotes[0, n_quotes / 2]
1163
+ while true
1164
+ quoted_value = @scanner.scan_all(@quoted_value)
1165
+ value << quoted_value if quoted_value
1166
+ if @backslash_quote
1167
+ if @scanner.scan(@escaped_backslash)
1168
+ if @scanner.scan(@escaped_quote)
1169
+ value << @quote_character
1170
+ else
1171
+ value << @backslash_character
1172
+ end
1173
+ next
1174
+ end
1175
+ end
1176
+
1177
+ quotes = @scanner.scan_all(@quotes)
1178
+ unless quotes
1179
+ ignore_broken_line
1180
+ message = "Unclosed quoted field"
1181
+ raise MalformedCSVError.new(message, @lineno)
1182
+ end
1183
+ n_quotes = quotes.size
1184
+ if n_quotes == 1
1185
+ break
1186
+ else
1187
+ value << quotes[0, n_quotes / 2]
1188
+ break if (n_quotes % 2) == 1
1189
+ end
1190
+ end
1191
+ value
1192
+ end
1193
+ end
1194
+
1195
+ def parse_column_end
1196
+ return true if @scanner.scan(@column_end)
1197
+ return false unless @column_ends
1198
+
1199
+ @scanner.keep_start
1200
+ if @column_ends.all? {|column_end| @scanner.scan(column_end)}
1201
+ @scanner.keep_drop
1202
+ true
1203
+ else
1204
+ @scanner.keep_back
1205
+ false
1206
+ end
1207
+ end
1208
+
1209
+ def parse_row_end
1210
+ return true if @scanner.scan(@row_end)
1211
+ return false unless @row_ends
1212
+ @scanner.keep_start
1213
+ if @row_ends.all? {|row_end| @scanner.scan(row_end)}
1214
+ @scanner.keep_drop
1215
+ true
1216
+ else
1217
+ @scanner.keep_back
1218
+ false
1219
+ end
1220
+ end
1221
+
1222
+ def strip_value(value)
1223
+ return value unless @strip
1224
+ return value if value.nil?
1225
+
1226
+ case @strip
1227
+ when String
1228
+ while value.delete_prefix!(@strip)
1229
+ # do nothing
1230
+ end
1231
+ while value.delete_suffix!(@strip)
1232
+ # do nothing
1233
+ end
1234
+ else
1235
+ value.strip!
1236
+ end
1237
+ value
1238
+ end
1239
+
1240
+ def ignore_broken_line
1241
+ @scanner.scan_all(@not_line_end)
1242
+ @scanner.scan_all(@line_end)
1243
+ @lineno += 1
1244
+ end
1245
+
1246
+ def start_row
1247
+ if @last_line
1248
+ @last_line = nil
1249
+ else
1250
+ @scanner.keep_drop
1251
+ end
1252
+ @scanner.keep_start
1253
+ end
1254
+
1255
+ def emit_row(row, quoted_fields=FieldsConverter::NO_QUOTED_FIELDS, &block)
1256
+ @lineno += 1
1257
+
1258
+ raw_row = row
1259
+ if @use_headers
1260
+ if @headers.nil?
1261
+ @headers = adjust_headers(row, quoted_fields)
1262
+ return unless @return_headers
1263
+ row = Row.new(@headers, row, true)
1264
+ else
1265
+ row = Row.new(@headers,
1266
+ @fields_converter.convert(raw_row, @headers, @lineno, quoted_fields))
1267
+ end
1268
+ else
1269
+ # convert fields, if needed...
1270
+ row = @fields_converter.convert(raw_row, nil, @lineno, quoted_fields)
1271
+ end
1272
+
1273
+ # inject unconverted fields and accessor, if requested...
1274
+ if @unconverted_fields and not row.respond_to?(:unconverted_fields)
1275
+ add_unconverted_fields(row, raw_row)
1276
+ end
1277
+
1278
+ yield(row)
1279
+ end
1280
+
1281
+ # This method injects an instance variable <tt>unconverted_fields</tt> into
1282
+ # +row+ and an accessor method for +row+ called unconverted_fields(). The
1283
+ # variable is set to the contents of +fields+.
1284
+ def add_unconverted_fields(row, fields)
1285
+ class << row
1286
+ attr_reader :unconverted_fields
1287
+ end
1288
+ row.instance_variable_set(:@unconverted_fields, fields)
1289
+ row
1290
+ end
1291
+ end
1292
+ end