brakeman 6.2.2 → 7.0.1

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